Type character to copy cv reference qualifiers

advertisements

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?


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.