【问题标题】:Type trait for copying cv reference qualifiers用于复制 cv 引用限定符的类型特征
【发布时间】:2015-09-19 05:19:46
【问题描述】:

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 && >{});

我使用两种方法为自己发明了它:通过类型限定符的 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

第一种方法看起来有点人为,但提供了一个“类型限定符 id”作为附加方面,后者在某些情况下可能很有用。第二种方法本质上是两步法。它可能有缺点。此外,还涉及std::remove_reference_t揭示cv限定类型。

一方面,我知道标准允许实现具有"intrinsic" type traits。另一方面,当前的 C++ 标准中没有类型特征。

copy_cv_reference_t 类型特征的最佳实现是什么?不仅在以上两者之间。有没有更好的方法来实现它?有没有相应的提案?

命名呢? id 的顺序呢?

【问题讨论】:

    标签: c++ c++11 c++14 typetraits c++17


    【解决方案1】:

    我还没有遇到任何需要这样的类型特征的用例,而且我不知道有任何提议。因此,我只能提供更紧凑且恕我直言更易于理解的实现:

    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

    您是否认为这是一种改进是主观的。

    【讨论】:

    • 代码中提出的用例允许在static_assert(std::is_convertible&lt; T, U &gt;{}); 时将T 类型的xvalue 完美地转发为U 类型的xvalue。 IE。某种展开(例如,对于变体、可选、元组等)。
    • 这里是另一个用例:假设您正在编写一个由class Container 模板化的类似 C++20 视图的迭代器工厂(它返回隐藏 @ 中的某些项目的特殊迭代器987654327@)。当您想在基于范围的情况下使用它时,它必须保留对Container 的引用并提供begin()/end()。工厂返回的迭代器operator*的返回类型是什么?应该是copy_cv_t&lt;Container, typename Container::value_type&gt;&amp;...
    【解决方案2】:

    我建议您将特征/元功能分解为两部分。首先,良好的关注点分离:传播 cv-qualifiers 和传播 ref-qualifiers 这两个任务确实是不同的。我有时也单独使用这两者。例如。 with pointers qualifying_cv_of_t&lt;A, B&gt;* 不时出现,在这种情况下,我们绝对不希望指向引用的指针,因为它们是无效的。 (我的特征被命名为qualifying_*_of_t&lt;A, B&gt;,可以理解为“A 的相关属性符合B 的相关属性”。)

    其次,后一个特征相当棘手要正确。也许您想机械地复制顶级引用(如果存在),在这种情况下没有什么可说的。另一方面,你说:

    [...] 某种展开(例如,对于变体、可选、元组等)[...]

    这绝对是我使用它的场景之一。我决定的一件事是,我真正关心的不是参考,而是价值类别。也就是说qualifying_t&lt;X, Y&gt;(传播一切的那个)在概念上代表decltype(expr.member)†,其中expr有类型

    struct X { Y member; };
    

    可能是 cv 合格的。该特征使得可以编写例如

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

    正确(假设 u 确实有类型 U),即使例如U 是一个引用类型。那么,这有多棘手?好吧,即使the Standard Committee 还没有弄清楚 C++14 → C++1z 转换的所有细节,以修复 C++11 → C++14 转换中引入的错误。我不会详细说明解决方案,因为我不相信一种尺寸适合所有人:例如。 std::tuple_element_tstd::get 形成了一个非常相似的特征/功能模板对,它们的作用与我上面概述的不同。

    好消息是您可以根据需要编写任意数量的特征,将其与您的 qualifying_cv_of 结合起来,您就可以开始了(事实上我自己也有两个这样的特征!)。所以也许真正的答案不是将特征一分为二,而是根据需要分成多少。


    †:目光敏锐的人可能已经注意到这里有什么东西,而是会假设像decltype( (expr.member) )这样的东西。至于哪个更可取,或者为什么,我还没有一个令人满意的答案。

    【讨论】:

      【解决方案3】:

      这是一个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>{})>;
      

      使用上述内容,您可以使用 tag&lt;T&gt; 类型或原始类型。

      从限定词中分离参考工作对我来说很有意义。

      想看文案吗?

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

      可以转换为类型:

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

      有点丑。

      我们可以用引用做同样的事情

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

      包括参考折叠

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

      等等。 (左值和右值限定符都以左值结尾)

      我们可以写add_ref_qualsub_ref_qual,用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;
      }
      

      我们去掉 ref 限定条件,然后添加 from 的 cv 限定条件,然后再添加 from 和 to 的 ref 限定条件。

      【讨论】:

      • 错字constexpr qual_t&lt;lhs|rhs&gt; opetator|(qual_t&lt;lhs&gt;,qual_t&lt;rhs&gt;){return {};}运营商
      • @puio 笑,我很惊讶那是唯一的错字。
      【解决方案4】:

      这是一个即插即用的解决方案:

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

      【讨论】:

        【解决方案5】:

        实现这一点的一种简洁方法是使用一个小助手实用程序,该实用程序根据某些条件应用元函数:

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

        这允许我们组合不同的 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>>>;
        

        【讨论】:

          猜你喜欢
          • 2021-11-16
          • 1970-01-01
          • 2016-04-17
          • 2023-02-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-28
          相关资源
          最近更新 更多