web-dev-qa-db-ja.com

テンプレートパラメータパックはN番目のタイプとN番目の要素にアクセスします

次のペーパーは、テンプレートパラメーターパックについて最初に見つけた提案です。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1603.pdf

16ページでは、パラメーターパック要素とパラメーターパックタイプにアクセスするための2つの新しい演算子[]と<>の導入について説明しています。

The suggested syntax for such an operator involves two new operators: .[] to access values and .<> to access types. For instance:

template<int N, typename Tuple> struct Tuple_element;
template<int N, ... Elements>
struct Tuple_element<Tuple<Elements...> >
{
    typedef Elements.<N> type;
};

template<int N, ... Elements>
Elements.<N>& get(Tuple<Elements...>& t)
{ return t.[N]; }

template<int N, ... Elements>
const Elements.<N>& get(const Tuple<Elements...>& t)
{ return t.[N]; }

では、これらの演算子はどこにありますか?ない場合、それらの代替品は何ですか?

36
nurettin

C++ 11には対応する演算子がないため、提案されています。 C++ 11では、対応する情報を自分で抽出するか、必要な操作を既に実行しているクラスを使用する必要があります。最も簡単な方法は、おそらく対応するロジックを既に実装しているstd::Tuple<T...>を使用することです。

std::Tuple<T...>が現在これらの操作をどのように実装しているのか不思議に思うなら、それは基本的にかなり悪い関数型プログラミング表記を使用した関数型プログラミングの練習です。シーケンスのn- thタイプを取得する方法がわかれば、インデックスとタイプでパラメーター化されたベースクラスからの継承を使用してn- th要素を取得するのは非常に簡単です。 Tuple_element<N, T...>のようなものを実装すると、次のようになります。

template <int N, typename... T>
struct Tuple_element;

template <typename T0, typename... T>
struct Tuple_element<0, T0, T...> {
    typedef T0 type;
};
template <int N, typename T0, typename... T>
struct Tuple_element<N, T0, T...> {
    typedef typename Tuple_element<N-1, T...>::type type;
};

std::Tuple<T...>のようなものを実装する際の実際のより困難なビットは、インデックスのリストを作成することです。そのため、たとえば(how内部の詳細の見た目はまったく異なりますが、型とそのインデックスの並列パラメーターパックを持つという基本的な考え方はそこにあります)。

template <typename... T, int... I>
class Tuple_base<Tuple_types<T...>, Tuple_indices<I...>>:
     public Tuple_field<T, I>... {
};
23
Dietmar Kühl

他の人はすでに、std::Tuple。パラメータパックのN番目のタイプにアクセスする場合は、次のメタ関数が便利です。

template<int N, typename... Ts> using NthTypeOf =
        typename std::Tuple_element<N, std::Tuple<Ts...>>::type;

使用法:

using ThirdType = NthTypeOf<2, Ts...>;
43
Emile Cormier

N番目の要素にアクセスしますか?

std::forward_as_Tupleを使用:

template <int I, class... Ts>
decltype(auto) get(Ts&&... ts) {
  return std::get<I>(std::forward_as_Tuple(ts...));
}

使用例:

template<class...Ts>
void foo(Ts&&...ts){

  auto& first = get<0>(ts...);
  auto second = get<1>(ts...);

  first = 'H';
  second = 'E';

  (std::cout << ... << ts);
}

foo('h','e','l','l','o');
// prints "Hello"

この答えは、n番目のタイプのみを提供するEmile Cormierの答えを補足するものです。

7
tom

パックからN番目の要素を取得するには、次のように記述できます。

オプション1

Tuple_elementを使用して、N番目の要素の戻り値の型を取得します。

template<size_t index, typename T, typename... Ts>
inline constexpr typename enable_if<index==0, T>::type
get(T&& t, Ts&&... ts) {
    return t;
}

template<size_t index, typename T, typename... Ts>
inline constexpr typename enable_if<(index > 0) && index <= sizeof...(Ts),
          typename Tuple_element<index, Tuple<T, Ts...>>::type>::type
get(T&& t, Ts&&... ts) {
    return get<index-1>(std::forward<Ts>(ts)...);
}

// below is optional - just for getting a more readable compilation error
// in case calling get with a bad index

inline template<long long index, typename... Ts>
constexpr bool index_ok() {
    return index >= 0 && index < sizeof...(Ts);
}

template<long long index, typename T, typename... Ts>
inline constexpr
typename enable_if<!index_ok<index, T, Ts...>(), T>::type
get(T&& t, Ts&&... ts) {
    static_assert(index_ok<index, T, Ts...>(),
        "bad index in call to get, smaller than zero or above pack size");
    return t;
}

オプション2

Tupleを使用せずに、auto return typeに依存し、特にC++ 14decltype(auto )およびenable_ifを戻り値の型としてではなくテンプレートパラメータとして使用する場合:

template<size_t index, typename T, typename... Ts,
    typename enable_if<index==0>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
    return std::forward<T>(t); 
}

template<size_t index, typename T, typename... Ts,
    typename enable_if<(index > 0 && index <= sizeof...(Ts))>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
    return get<index-1>(std::forward<Ts>(ts)...);
}

template<long long index, typename... Ts>
inline constexpr bool index_ok() {
    return index >= 0 && index < (long long)sizeof...(Ts);
}

// block (compilation error) the call to get with bad index,
// providing a readable compilation error
template<long long index, typename T, typename... Ts,
    typename enable_if<(!index_ok<index, T, Ts...>())>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
    static_assert(index_ok<index, T, Ts...>(),
        "bad index in call to get, smaller than zero or above pack size");
    return std::forward<T>(t); // need to return something...
                               // we hope to fail on the static_assert above
}

使用例:

template<size_t index, typename... Ts>
void resetElementN(Ts&&... ts) {
    get<index>(std::forward<Ts>(ts)...) = {}; // assuming element N has an empty ctor
}

int main() {
    int i = 0;
    string s = "hello";
    get<0>(i,2,"hello","hello"s, 'a') += get<0>(2);
    get<1>(1,i,"hello",4) += get<1>(1, 2);
    get<3>(1,2,"hello",i) += get<2>(0, 1, 2);    
    get<2>(1,2,s,4) = get<2>(0, 1, "hi");
    cout << i << ' ' << s << endl;    
    resetElementN<1>(0, i, 2);
    resetElementN<0>(s, 1, 2);
    cout << i << ' ' << s << endl;    

    // not ok - and do not compile
    // get<0>(1,i,"hello","hello"s) = 5;
    // get<1>(1,i*2,"hello") = 5;
    // get<2>(1,i*2,"hello")[4] = '!';
    // resetElementN<1>(s, 1, 2);

    // ok
    const int j = 2;
    cout << get<0>(j,i,3,4) << endl;

    // not ok - and do not compile
    // get<0>(j,i,3,4) = 5;    

    // not ok - and do not compile
    // with a readable compilation error
    // cout << get<-1>("one", 2, '3') << endl;
    // cout << get<3>("one", 2, '3') << endl;
}

コード
オプション1: http://coliru.stacked-crooked.com/a/60ad3d860aa9445
オプション2: http://coliru.stacked-crooked.com/a/09f6e8e155612f8b

5
Amir Kirsh

単純な関数を実装して、n番目のパラメーターを直接を再帰呼び出しなしで取得できますが、コンパイル時に多くの純粋な型操作を実行できます。最初にキーコードを見てみましょう。

template<class...Ts>
struct GetImp {
  template<class T, class...Us>
  static decltype(auto) impl(Ts&&..., T&& obj, Us&&...) {
    return std::forward<T>(obj);
  }
};

template<size_t n, class...Ts>
decltype(auto) get(Ts&&...args) {
  static_assert(n<sizeof...(args), "index over range");
  return Transform<GetImp, Before_s<n, Seq<Ts...>> >
    ::impl(std::forward<Ts>(args)...);
}

変換とはどういう意味ですか?

たとえば、std::Tuple<int,double,float>であるタイプTがある場合、Transform<GetImp,T>GetImp<int,double,float>になります。 std::Tupleの代わりに別の空の構造体 "Seq"を定義して、コンパイル時間を短縮して同じことを行うことに注意してください(実際、両方とも非常に迅速にコンパイルできますが、空の構造体の方が効果的です) Before_s<n,Seq<Ts...>>を生成してSeq<?>を取得し、それをGetImpに変換し、[0]〜[n-1]パラメーターのタイプを確認してから、n番目のインデックスにドロップしますパラメータを直接。たとえば、Before_s<3,Seq<T0,T1,T2,T3,T4...>>Seq<T0,T1,T2>Before_s<2,Seq<T0,T1,T2,T3,T4...>>Seq<T0,T1>などです。1つのメタ関数を使用して別のメタ関数を実装する場合、Before_sを使用してSeq型を処理し、コンパイル時間を短縮しますコンパイル時間を短縮するためのメタ関数。

実装

#define OMIT_T(...) typename __VA_ARGS__::type

template<class...Args>
struct Seq { };

template< template<class...> class Dst >
struct TransformImp{
    template< template<class...>class Src, class...Args >
    static Dst<Args...> from(Src<Args...>&&);
};
template< template<class...> class Dst, class T>
using Transform = decltype(TransformImp<Dst>::from(std::declval<T>()));
template<class T>
using Seqfy = Transform<Seq, T>;


template<class...>struct MergeImp;
template<class...Ts, class...Others>
struct MergeImp<Seq<Ts...>, Seq<Others...>>
{
  using type = Seq<Ts..., Others...>;
};
template<class first, class second>
using Merge = OMIT_T(MergeImp<Seqfy<first>, Seqfy<second> >);
template<class T, class U>
using Merge_s = OMIT_T(MergeImp<T, U>);

template<size_t, class...>struct BeforeImp;

template<size_t n, class T, class...Ts>
struct BeforeImp<n, Seq<T, Ts...>> {
    static_assert(n <= sizeof...(Ts)+1, "index over range");
    using type = Merge_s<Seq<T>, OMIT_T(BeforeImp<n - 1, Seq<Ts...>>)>;
};

template<class T, class...Ts>
struct BeforeImp<1, Seq<T, Ts...>> {
    using type = Seq<T>;
};
template<class T, class...Ts>
struct BeforeImp<0, Seq<T, Ts...>> {
    using type = Seq<>;
};
template<size_t n>
struct BeforeImp<n, Seq<>> {
    using type = Seq<>;
};

template<size_t n, class T>
using Before = OMIT_T(BeforeImp<n, Seqfy<T>>);
template<size_t n, class T>
using Before_s = OMIT_T(BeforeImp<n, T>);

編集:高度な実装

Before_sを使用してn-1型をn番目の型の前に計算する必要はありませんが、代わりにそれらを無視できます。

struct EatParam{
    constexpr EatParam(...)noexcept{}
};

template<size_t n>
struct GenSeqImp {
  using type = Merge_s<OMIT_T(GenSeqImp<n / 2>), OMIT_T(GenSeqImp<n - n / 2>)>;
};
template<>
struct GenSeqImp<0> {
  using type = Seq<>;
};
template<>
struct GenSeqImp<1> {
  using type = Seq<EatParam>;
};

template<size_t n>
using GenSeq = OMIT_T(GenSeqImp<n>);


template<class...Ts>
struct GetImp {
  template<class T>
  static constexpr decltype(auto) impl(Ts&&..., T&& obj, ...)noexcept {
    return std::forward<T>(obj);
  }
};


template<size_t n, class...Ts>
constexpr decltype(auto) get(Ts&&...args)noexcept {
  static_assert(n<sizeof...(args), "index over range.");
  //return Transform<GetImp, Before_s<n, Seq<Ts...>> >
  return Transform<GetImp, GenSeq<n>>
    ::impl(std::forward<Ts>(args)...);
}

さらに、 there は、n番目の型を取得する実装に関する非常に興味深い記事です。

彼らの仕事のおかげで、ハックをするために(...)を使用できるとは知りませんでした。

2
Landersing