7

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 のみを使用する方法の 2 つの方法を使用して、自分でそれを発明しました。

#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」を提供し、後者は状況によっては便利です。2 番目のアプローチは、本質的に 2 段階のアプローチです。それには欠点があるかもしれません。さらにstd::remove_reference_t、cv 修飾された型を明らかにする必要があります。

一方では、標準では実装が「組み込み」型の特性を持つことが許可されていることを知っています。一方、現在のC++標準には型特性はありません。

copy_cv_reference_t型特性の最適な実装は何ですか? 上記の2つだけではありません。それを実装するためのより良いアプローチはありますか? 対応する提案はありますか?

ネーミングはどうする?IDの順番は?

4

5 に答える 5

7

そのような型特性を必要とするユースケースに遭遇したことはなく、提案も知りません。したがって、私はよりコンパクトで理解しやすい実装のみを提供できます。

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;

実際の例

改善されたかどうかは主観的なものです。

于 2015-07-01T23:03:11.703 に答える
3

これは、boost::hana参照ではなく、修飾子のための風変わりなシステムです。

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<T>タイプまたは生のタイプを操作できます。

レファレンス作品を修飾子から切り離すことは、私には理にかなっています。

コピーを見たいですか?

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_qualand sub_ref_qual、オーバーロード+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;
}

ここで、ref 修飾を to から取り除き、次に from の cv 修飾を追加してから、from と to の両方の ref 修飾を再び追加します。

于 2015-07-03T01:41:39.143 に答える
2

トレイト/メタファンクションを 2 つに分解することをお勧めします。まず第一に、これは問題の適切な分離です。cv 修飾子の伝搬と ref 修飾子の伝搬という 2 つのタスクは実際には異なります。2 つを単独で使用することもあります。たとえば with ポインターがときどきqualifying_cv_of_t<A, B>*出てきますが、その場合、参照へのポインターは無効であるため絶対に必要ありません。(私の特性は、「の関連するプロパティがのそれらを修飾しているqualifying_*_of_t<A, B>」という意味で理解できる名前が付けられています。)AB

第二に、後者の特性は正しく理解するのがかなり難しいです。トップレベルの参照 (存在する場合) を機械的にコピーしたい場合がありますが、その場合は特に説明する必要はありません。一方、あなたは次のように言います。

[…] ある種のアンラップ (たとえば、バリアント、オプション、タプルなど) […]

これは間違いなく私が使用するシナリオの 1 つです。私が決めたことの 1 つは、実際に気にするのは参照ではなく、値のカテゴリだということです。つまり、qualifying_t<X, Y>(すべてを伝播するもの) は概念的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; }

egが参照型であっても、正しく ( type があると仮定uして)。それで、それはどれほどトリッキーですか?まあ、標準委員会でさえ、C++11 → C++14 移行で導入されたバグを修正するために、C++14 → C++1z 移行のすべての詳細をまだ把握していません。1 つのサイズですべてに対応できるとは思わないため、解決策を詳しく説明することはしません。たとえば、上で概説したものとは異なることを行う、非常によく似た特性/関数テンプレートのペアを形成します。UUstd::tuple_element_tstd::get

良いことは、必要なだけ多くのトレイトを記述し、それを自分のトレイトと組み合わせることができるということですqualifying_cv_of(実際、私はそのようなトレイトを 2 つ持っています!)。したがって、本当の答えは、特性を 2 つに分割することではなく、必要なだけ分割することです。


†: 鋭い目を持つ人は、ここで何かに気づいた可能性があり、代わりにdecltype( (expr.member) ). どちらが好ましいか、またはその理由について、私はまだ満足のいく答えを持っていません。

于 2015-07-02T02:53:57.057 に答える
1

あなたの問題に対するプラグアンドプレイの解決策は次のとおりです。

#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 && >{}, "!");
}
于 2016-11-06T22:07:22.340 に答える