Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type trait for copying cv reference qualifiers

Writing library-like code in C++ I found there is particular need in copy_cv_reference_t type trait:

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 invent it for myself using two approaches: via means of id of type qualifiers and via SFINAE only.

#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

First approach looks slightly more artificial, but provides a "type qualifiers id" as additional side and latter can be useful in some situations. Second approach is inherently two-step one. It could has downsides. In addition, it involve std::remove_reference_t to reveal the cv-qualified type.

On the one hand, I know standard allows for implementations to have an "intrinsic" type traits. On the other hand, there is no the type trait currently in contemporary C++ standard.

What is the best implementation of copy_cv_reference_t type trait? Not only between above two. Are there better approaches to implement it? Is there corresponding proposal?

What about naming? What about order of ids?

like image 655
Tomilov Anatoliy Avatar asked Jul 01 '15 21:07

Tomilov Anatoliy


3 Answers

I haven't encountered any use-case which required a type-trait like that and I am not aware of any proposal. I can therefore only offer an implementation which is more compact and IMHO more simple 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;

Live example

Whether you find it an improvement is subjective.

like image 105
Daniel Frey Avatar answered Sep 29 '22 20:09

Daniel Frey


I advise you to decompose your trait/metafunction in two. First of all, it’s good separation of concerns: the two tasks of propagating cv-qualifiers and propagating ref-qualifiers really are different. I sometimes use of the two in isolation, too. E.g. with pointers qualifying_cv_of_t<A, B>* comes up from time to time, in which case we absolutely don’t want pointers to references as those are invalid. (My traits are named qualifying_*_of_t<A, B> which can be understood to mean 'the relevant properties of A are qualifying those of B'.)

Second, the latter trait is rather tricky to get right. Maybe you want to mechanically copy the top-level reference (if present), in which case there isn’t much to say about it. On the other hand, you say:

[…] some kind of unwrapping (say, for variant, optional, tuple etc.) […]

which is definitively one of the scenarios where I use it. One of the thing I’ve decided is that it’s not actually references that I care about, it’s value category. That is to say, qualifying_t<X, Y> (the one that propagates everything) conceptually represents decltype(expr.member)† where expr has type

struct X { Y member; };

possibly cv-qualified. The trait makes it possible to write e.g.

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

correctly (assuming u does have type U), even if e.g. U is a reference type. So, how tricky is that? Well, even the Standard Committee has yet to figure out all the details for the C++14 → C++1z transition to fix a bug introduced in the C++11 → C++14 transition. I won’t spell out a solution because I don’t believe one size fits all: e.g. std::tuple_element_t and std::get form a very similar trait/function template pair that does something different than what I outlined above.

The good thing is that you can write as many trait as you need, combine it with your qualifying_cv_of and you are good to go (and I fact I have two such traits myself!). So maybe the real answer is not to split the trait in two, but in however many you need.


†: the keen-eyed may have noticed something off here and would have instead assumed something like decltype( (expr.member) ). I do not have a satisfactory answer yet as to which is preferable, or why.

like image 23
Luc Danton Avatar answered Sep 29 '22 18:09

Luc Danton


Here is a boost::hana esque system for qualifiers, not references.

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> operator|(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.

Divorcing reference work from qualifier makes sense to me.

Want to see 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 bit more ugly.

We can do the exact same thing with references

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

including reference collapsing

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 lvalue and rvalue qualifiers ends with lvalue)

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

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 strip the ref qualification off to, then add the cv qualification of from, then add back in the ref qualifiactions of both from and to.

like image 31
Yakk - Adam Nevraumont Avatar answered Sep 29 '22 19:09

Yakk - Adam Nevraumont