简体   繁体   English

用于复制 cv 引用限定符的类型特征

[英]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:C++ 中编写类库代码我发现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 invent it for myself using two approaches: via means of id of type qualifiers and via SFINAE only.我使用两种方法为自己发明了它:通过类型限定符的 id 和仅通过 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

First approach looks slightly more artificial, but provides a "type qualifiers id" as additional side and latter can be useful in some situations.第一种方法看起来有点人为,但提供了“类型限定符 id”作为附加方面,后者在某些情况下可能很有用。 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.此外,它还涉及std::remove_reference_t以显示 cv 限定类型。

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.另一方面,当代C++标准中目前没有类型特征。

What is the best implementation of copy_cv_reference_t type trait? copy_cv_reference_t类型特征的最佳实现是什么? Not only between above two.不仅介于以上两者之间。 Are there better approaches to implement it?有没有更好的方法来实现它? Is there corresponding proposal?有没有相应的建议?

What about naming?取名呢? What about order of ids? id的顺序呢?

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.您是否发现它有所改进是主观的。

Here is a boost::hana esque system for qualifiers, not references.这是用于限定符的boost::hana esque 系统,而不是引用。

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.使用上述内容,您可以使用tag<T>类型或原始类型。

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 tag s.我们可以写add_ref_qualsub_ref_qual ,重载+-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 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.我们去掉 ref 限定 to,然后添加 from 的 cv 限定,然后添加回 from 和 to 的 ref 限定。

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.首先,这是一个很好的关注点分离:传播 cv-qualifiers 和传播 ref-qualifiers 这两个任务确实是不同的。 I sometimes use of the two in isolation, too.我有时也单独使用这两者。 Eg 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.例如,指针qualifying_cv_of_t<A, B>*不时出现,在这种情况下,我们绝对不希望指向引用的指针,因为它们是无效的。 (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 '.) (我的特征被命名为qualifying_*_of_t<A, B>可以理解为' A的相关属性正在限定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也就是说, qualifying_t<X, Y> (传播一切的那个)在概念上代表decltype(expr.member) † 其中expr有类型

struct X { Y member; };

possibly cv-qualified.可能符合 cv 资格。 The trait makes it possible to write eg该特性使得可以编写例如

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 eg U is a reference type.正确(假设u确实有类型U ),即使例如U是引用类型。 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.好吧,即使是标准委员会也没有弄清楚 C++14 → C++1z 转换的所有细节,以修复 C++11 → C++14 转换中引入的错误。 I won't spell out a solution because I don't believe one size fits all: eg std::tuple_element_t and std::get form a very similar trait/function template pair that does something different than what I outlined above.我不会详细说明解决方案,因为我不相信一种尺寸适合所有人:例如std::tuple_element_tstd::get形成了一个非常相似的特征/函数模板对,它们的作用与我上面概述的有所不同。

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!).好消息是,您可以根据需要编写任意数量的 trait,将它与您的qualifying_cv_of结合起来就可以了(事实上我自己也有两个这样的特征!)。 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) ) . †:敏锐的眼睛可能已经注意到这里的一些东西,并且会假设像decltype( (expr.member) )这样的东西。 I do not have a satisfactory answer yet as to which is preferable, or why.至于哪个更可取,或者为什么,我还没有一个令人满意的答案。

Here is a plug and play solution to your problem:这是您的问题的即插即用解决方案:

#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 && >{}, "!");
}

A concise way to implement this is with a little helper utility that applies a 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 compose the different cvref qualifiers:这允许我们组合不同的 cvref 限定符:

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>>>;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM