Typical attribute for copying cv link specifiers

Writing library-like code in C ++, I found that there is a special need for copy_cv_reference_t type copy_cv_reference_t :

 struct A; struct B; static_assert(std::is_same< copy_cv_reference_t< A , B >, B >{}); static_assert(std::is_same< copy_cv_reference_t< A const , B >, B const >{}); static_assert(std::is_same< copy_cv_reference_t< volatile A , B >, volatile B >{}); static_assert(std::is_same< copy_cv_reference_t< volatile A const , B >, volatile B const >{}); static_assert(std::is_same< copy_cv_reference_t< A &, B >, B & >{}); static_assert(std::is_same< copy_cv_reference_t< A const &, B >, B const & >{}); static_assert(std::is_same< copy_cv_reference_t< volatile A &, B >, volatile B & >{}); static_assert(std::is_same< copy_cv_reference_t< volatile A const &, B >, volatile B const & >{}); static_assert(std::is_same< copy_cv_reference_t< A &&, B >, B && >{}); static_assert(std::is_same< copy_cv_reference_t< A const &&, B >, B const && >{}); static_assert(std::is_same< copy_cv_reference_t< volatile A &&, B >, volatile B && >{}); static_assert(std::is_same< copy_cv_reference_t< volatile A const &&, B >, volatile B const && >{}); 

I came up with this for myself, using two approaches: using an identifier like determinants and only through SFINAE.

 #include <type_traits> #if 1 enum class type_qual_id { value, const_value, lref, const_lref, rref, const_rref, volatile_value, volatile_const_value, volatile_lref, volatile_const_lref, volatile_rref, volatile_const_rref, }; template< type_qual_id tqid, typename type > struct add_type_qualifier; template< typename to > struct add_type_qualifier< type_qual_id::value , to > { using type = to ; }; template< typename to > struct add_type_qualifier< type_qual_id::const_value , to > { using type = to const ; }; template< typename to > struct add_type_qualifier< type_qual_id::lref , to > { using type = to & ; }; template< typename to > struct add_type_qualifier< type_qual_id::const_lref , to > { using type = to const & ; }; template< typename to > struct add_type_qualifier< type_qual_id::rref , to > { using type = to &&; }; template< typename to > struct add_type_qualifier< type_qual_id::const_rref , to > { using type = to const &&; }; template< typename to > struct add_type_qualifier< type_qual_id::volatile_value , to > { using type = volatile to ; }; template< typename to > struct add_type_qualifier< type_qual_id::volatile_const_value, to > { using type = volatile to const ; }; template< typename to > struct add_type_qualifier< type_qual_id::volatile_lref , to > { using type = volatile to & ; }; template< typename to > struct add_type_qualifier< type_qual_id::volatile_const_lref , to > { using type = volatile to const & ; }; template< typename to > struct add_type_qualifier< type_qual_id::volatile_rref , to > { using type = volatile to &&; }; template< typename to > struct add_type_qualifier< type_qual_id::volatile_const_rref , to > { using type = volatile to const &&; }; template< type_qual_id tqid, typename to > using add_qualifier_t = typename add_type_qualifier< tqid, to >::type; template< typename type > constexpr type_qual_id get_type_qualifier_id = type_qual_id::value ; template< typename type > constexpr type_qual_id get_type_qualifier_id< type const > = type_qual_id::const_value ; template< typename type > constexpr type_qual_id get_type_qualifier_id< type & > = type_qual_id::lref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< type const & > = type_qual_id::const_lref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< type && > = type_qual_id::rref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< type const && > = type_qual_id::const_rref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< volatile type > = type_qual_id::volatile_value ; template< typename type > constexpr type_qual_id get_type_qualifier_id< volatile type const > = type_qual_id::volatile_const_value; template< typename type > constexpr type_qual_id get_type_qualifier_id< volatile type & > = type_qual_id::volatile_lref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< volatile type const & > = type_qual_id::volatile_const_lref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< volatile type && > = type_qual_id::volatile_rref ; template< typename type > constexpr type_qual_id get_type_qualifier_id< volatile type const && > = type_qual_id::volatile_const_rref ; template< typename from, typename to > using copy_cv_reference_t = add_qualifier_t< get_type_qualifier_id< from >, to >; #else #include <type_traits> template< typename from, typename to > struct copy_cv { using type = to; }; template< typename from, typename to > struct copy_cv< from const, to > : copy_cv< from, to const > { }; template< typename from, typename to > struct copy_cv< volatile from, to > : copy_cv< from, volatile to > { }; template< typename from, typename to > struct copy_cv< volatile from const, to > : copy_cv< from, volatile to const > { }; template< typename from, typename to > struct copy_reference { using type = to; }; template< typename from, typename to > struct copy_reference< from &, to > : copy_reference< from, to & > { }; template< typename from, typename to > struct copy_reference< from &&, to > : copy_reference< from, to && > { }; template< typename from, typename to > using copy_cv_reference_t = typename copy_reference< from, typename copy_cv< std::remove_reference_t< from >, to >::type >::type; #endif 

The first approach looks somewhat more artificial, but provides a "type qualifier identifier" as an additional side, and the latter may be useful in some situations. The second approach is inherently two-step. This may have disadvantages. In addition, it uses std::remove_reference_t to identify the cv-qualified type.

On the one hand, I know that the standard allows implementations to have traits of a "built-in" type . On the other hand, in the modern C ++ standard there is no typical feature.

What is the best implementation of copy_cv_reference_t type copy_cv_reference_t ? Not only between the two. Are there any better approaches to implement it? Is there a suggestion?

What about the names? What about the order of identifiers?

+7
source share
5 answers

I have not seen a single use case that would require such a type trait, and I do not know any suggestions. Therefore, I can only offer an implementation that is more compact and IMHO easier to understand:

 template<typename T,typename U> struct copy_cv_reference { private: using R = std::remove_reference_t<T>; using U1 = std::conditional_t<std::is_const<R>::value, std::add_const_t<U>, U>; using U2 = std::conditional_t<std::is_volatile<R>::value, std::add_volatile_t<U1>, U1>; using U3 = std::conditional_t<std::is_lvalue_reference<T>::value, std::add_lvalue_reference_t<U2>, U2>; using U4 = std::conditional_t<std::is_rvalue_reference<T>::value, std::add_rvalue_reference_t<U3>, U3>; public: using type = U4; }; template<typename T,typename U> using copy_cv_reference_t = typename copy_cv_reference<T,U>::type; 

Living example

Do you consider this improvement subjective.

+5
source

Here is the boost::hana esque system for qualifiers, not links.

 enum class qualifier:unsigned char { none, is_const = 1<<1, is_volatile = 1<<2, }; constexpr inline qualifier operator|(qualifier lhs,qualifier rhs){ return qualifier( unsigned(lhs)|unsigned(rhs) ); } constexpr inline bool operator&(qualifier lhs,qualifier rhs){ return unsigned(lhs)&unsigned(rhs); } // not a simple alias to make operator overloading work right: template<qualifier q> struct qual_t:std::integral_constant<qualifier,q> {}; template<qualifier lhs, qualifier rhs> constexpr qual_t<lhs|rhs> opetator|(qual_t<lhs>,qual_t<rhs>){return {};} template<class T>struct tag{using type=T;}; template<class Tag>using type_t=typename Tag::type; template<class T> constexpr qual_t< (std::is_const<T>{}?qualifier::is_const:qualifier::none) |(std::is_volatile<T>{}?qualifier::is_volatile:qualifier::none) > qual(tag<T>={}){ return {}; } template<class B, qualifier q, class Step1=std::conditional_t<q&qualifier::is_const,const B,B>, class R=std::conditional_t<q&qualifier::is_volatile,volatile Step1, Step1> > constexpr tag<R> add_qual( tag<B>={}, qual_t<q>={} ){ return {}; } template<class T,qualifier Q> auto operator+( tag<T> t, qual_t<Q> q ){ return add_qual(t,q); } template<class B, qualifier q> using add_qual_t=type_t<decltype(tag<B>{}+qual_t<q>{})>; 

using the above, you can work with tag<T> types or raw types.

Separation of reference work from the classifier makes sense to me.

Want to see a copy?

 template<class From, class To> constexpr auto copy_qual(tag<From> from={}, tag<To> to={}){ return to + qual(from); } 

which can be converted to types:

 template<class From, class To> using copy_qual_t=type_t<decltype(copy_qual<From,To>())>; 

with a little more ugly.

We can do the same with links.

 enum class ref_qualifier:unsigned char { none, rvalue, lvalue }; 

including collapse reduction

 constexpr inline ref_qualfier operator|(ref_qualifier lhs, ref_qualifier rhs){ return ((unsigned)lhs>(unsigned)rhs)?lhs:rhs; } constexpr inline ref_qualfier operator&(ref_qualifier lhs, ref_qualifier rhs){ return ((unsigned)lhs>(unsigned)rhs)?rhs:lhs; } 

etc .. (both qualifiers lvalue and rvalue end with lvalue)

We can write add_ref_qual and sub_ref_qual , overload + and - with tag s.

 template<class From, class To> constexpr auto copy_ref_and_quals( tag<From> from, tag<To> to ) { auto from_ref = ref_qual(from); auto from_cv = qual(from-from_ref); auto to_ref = ref_qual(to); return (to-to_ref)+from_cv+to_ref+from_ref; } 

where we drop the ref qualification, then add the cv qualification from, and then add back qual refiactions both from and to.

+3
source

I advise you to decompose your feature / metafunction in half. First of all, this is a good separation of problems: the two tasks of distributing cv qualifiers and distributing ref qualifiers are really different. Sometimes I use them in isolation. For instance. with qualifying_cv_of_t<A, B>* pointers arises from time to time, and in this case we absolutely do not want the link pointers to be incorrect. (My traits are called qualifying_*_of_t<A, B> , which can be understood as "the corresponding properties of A meet the criteria of B ".)

Secondly, the last trait is quite difficult to get right. Perhaps you want to mechanically copy the top-level link (if there is one), in which case there is nothing to say about it. On the other hand, you say:

[...] some scan (say, for a variant, optional, tuple, etc.) [...]

which is finally one of the scenarios where I use it. One of the things I decided is that these are not really the links that concern me, its value category. That is, qualifying_t<X, Y> (the one that is distributed to everyone) conceptually represents decltype(expr.member) †, where expr is of type

 struct X { Y member; }; 

maybe cv-qualified. This feature allows you to write, for example,

 template<typename T> qualifying_t<T, U> foo(T&& t) { return std::forward<T>(t).u; } 

correctly (it is assumed that u is of type u ), even if, for example, u is a reference type. So how complicated is this? Well, even the Standard Committee has not yet determined all the details for switching to C ++ 14 β†’ C ++ 1z in order to correct the error introduced in C ++ 11 β†’ C ++ 14. I will not name the solution because I do not believe that One size fits all, for example. std::tuple_element_t and std::get form a very close pair of feature / function patterns, which does something different from what I stated above.

It’s good that you can write as many traits as you need, combine it with your qualifying_cv_of , and you are good to go (and I am a fact that I have two such traits myself!). So, perhaps, the real answer is not to divide the attribute into two, but as much as you like.


†: the keen eye may have noticed something here and instead would have accepted something like decltype( (expr.member) ) . I do not yet have a satisfactory answer as to which is preferred or why.

+2
source

The following is a solution for connecting to a network:

 #include<type_traits> #include<cstddef> static const std::size_t N = 42; template<std::size_t N> struct choice: choice<N-1> {}; template<> struct choice<0> {}; template<typename T, typename U> struct types { using basic = T; using decorated = U; }; template<typename T, typename U> auto f(choice<0>) { return types<T, U>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_lvalue_reference<T>::value>> auto f(choice<1>) { auto t = f<std::remove_reference_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_lvalue_reference_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_rvalue_reference<T>::value>> auto f(choice<2>) { auto t = f<std::remove_reference_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_rvalue_reference_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_const<T>::value>> auto f(choice<3>) { auto t = f<std::remove_const_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_const_t<D>>{}; } template<typename T, typename U, typename = std::enable_if_t<std::is_volatile<T>::value>> auto f(choice<4>) { auto t = f<std::remove_volatile_t<T>, U>(choice<N>{}); using B = typename decltype(t)::basic; using D = typename decltype(t)::decorated; return types<B, std::add_volatile_t<D>>{}; } template<typename T, typename U> auto f() { return f<T, U>(choice<N>{}); } template<typename T, typename U = char> using copy_cv_reference_t = typename decltype(f<T, U>())::decorated; struct A; struct B; int main() { static_assert(std::is_same< copy_cv_reference_t< A , B >, B >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< A const , B >, B const >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< volatile A , B >, volatile B >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< volatile A const , B >, volatile B const >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< A &, B >, B & >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< A const &, B >, B const & >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< volatile A &, B >, volatile B & >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< volatile A const &, B >, volatile B const & >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< A &&, B >, B && >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< A const &&, B >, B const && >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< volatile A &&, B >, volatile B && >{}, "!"); static_assert(std::is_same< copy_cv_reference_t< volatile A const &&, B >, volatile B const && >{}, "!"); } 
+1
source

A short way to implement this is with a small helper utility that applies metafunction based on some condition:

 template <template <typename...> class MFn, bool condition, typename T> using apply_if_t = std::conditional_t<condition, MFn<T>, T>; 

This allows us to compile various cvref qualifiers:

 template <typename T> using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>; template <typename From, typename To> using copy_cv_t = apply_if_t<std::add_volatile_t, std::is_volatile_v<From>, apply_if_t<std::add_const_t, std::is_const_v<From>, std::remove_cv_t<To>>>; template <typename From, typename To> using copy_ref_t = apply_if_t<std::add_rvalue_reference_t, std::is_rvalue_reference_t<From>, apply_if_t<std::add_lvalue_reference_t, std::is_lvalue_reference_t<From>, std::remove_reference_t<To>>>; template <typename From, typename To> using copy_cvref_t = copy_ref_t<From, copy_cv_t<std::remove_reference_t<From>, remove_cvref_t<To>>>; 
0
source

All Articles