【问题标题】:template parameter packs access Nth type and Nth element模板参数包访问第 N 个类型和第 N 个元素
【发布时间】:2013-12-08 09:33:33
【问题描述】:

以下论文是我找到的关于模板参数包的第一个提案。

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

在第 16 页,它谈到了引入两个新的运算符 [] 和 来访问参数包元素和参数包类型。

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

那么这些运算符在哪里?如果没有,他们的替代品是什么?

【问题讨论】:

  • 他们的替换大概是把参数包放入std::tuple,使用std::getstd::tuple_element,递归实现。另见this answer
  • @DyP: std::getstd::tuple_element 本质上不需要递归,它们可以通过索引和重载解析/派生到基转换而无需递归来实现。
  • @Xeo:由于可变参数列表 [当前] 不支持按索引访问,我不知道有任何实现既不是递归也不是输出所有类型(然后将回退递归方法来涵盖理论上无限量的参数)。虽然我会通过基类实现成员访问,但确定要访问的正确类型将使用std::tuple_element&lt;...&gt;,这将是递归函数。
  • @Xeo 正如您从链接答案下方的答案中看到的那样,我 确实 实现了与 std::tuple_element 等效的类型推导和索引 - 使用您的 O(logN ) 索引生成器 ;) 但是需要递归(或手动)创建索引。

标签: c++ c++11 variadic-templates


【解决方案1】:

C++11 没有相应的运算符,这就是它们被提出的原因。使用 C++11,您需要自己提取相应的信息,或者使用已经完成必要操作的类。最简单的方法可能就是使用已经实现了相应逻辑的std::tuple&lt;T...&gt;

如果您想知道std::tuple&lt;T...&gt; 目前是如何实现这些操作的:它基本上是一个使用相当糟糕的函数式编程符号的函数式编程练习。一旦您知道如何获取序列的n-th 类型,使用从索引和类型参数化的基类继承来获取n-th 元素就相当简单了。实现类似tuple_element&lt;N, T...&gt; 的东西可能看起来像这样:

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&lt;T...&gt; 之类的东西时,实际更具挑战性的一点是变出一个索引列表,这样你就可以得到一个类型和整数的并行列表,然后可以对其进行扩展,例如,对于使用类似 (内部细节看起来如何会有所不同,但为类型及其索引提供并行参数包的基本思想会以某种方式存在):

template <typename... T, int... I>
class tuple_base<tuple_types<T...>, tuple_indices<I...>>:
     public tuple_field<T, I>... {
};

【讨论】:

    【解决方案2】:

    其他人已经回答可以通过std::tuple完成。如果您想访问第 N 种类型的参数包,您可能会发现以下元函数很方便:

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

    用法:

    using ThirdType = NthTypeOf<2, Ts...>;
    

    【讨论】:

    • 有没有办法让它在运行时工作?即它接受非 constexpr 作为第一个参数?
    • @narengi 我需要了解更多关于你想要做什么的信息。最好是单独询问这些详细信息(如果尚未询问)。
    【解决方案3】:

    要从包中获取第 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 返回类型,特别是 C++14 decltype(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/60ad3d860aa94453
    选项 2:http://coliru.stacked-crooked.com/a/09f6e8e155612f8b

    【讨论】:

      【解决方案4】:

      我们可以实现一个简单的函数来直接获取第 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)...);
      }
      

      变换是什么意思?

      例如,如果我们有一个类型T,即std::tuple&lt;int,double,float&gt;, 那么Transform&lt;GetImp,T&gt; 将是GetImp&lt;int,double,float&gt;。 请注意,我定义了另一个空结构“Seq”而不是 std::tuple 来做 相同的东西,编译时间更短。(实际上它们都可以很快编译,但我想空结构会更有效) 所以Before_s&lt;n,Seq&lt;Ts...&gt;&gt;生成一个Seq&lt;?&gt;然后我们把它转化为GetImp,这样我们就可以知道[0]~[n-1]参数是什么类型的, 然后将它们放下以直接索引第 n 个参数。 例如,Before_s&lt;3,Seq&lt;T0,T1,T2,T3,T4...&gt;&gt;Seq&lt;T0,T1,T2&gt;Before_s&lt;2,Seq&lt;T0,T1,T2,T3,T4...&gt;&gt;Seq&lt;T0,T1&gt; 等等。 我们使用 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 个类型之前的 n-1 个类型,而是, 我们可以忽略它们:

      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 种类型的实现:

      感谢他们的工作,我以前不知道我们可以使用 (...) 来进行破解。

      【讨论】:

        【解决方案5】:

        访问第 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"
        

        此答案是对 Emile Cormier 仅给出第 n 种类型的答案的补充。

        【讨论】:

        • ts 应该总是向前传递:forward&lt;Ts&gt;(ts)...
        • 您能解释一下原因吗?我相信在这种情况下没关系,ts... 的元素是 char,所以额外的复制而不是移动无关紧要。事实上它可能是有害的如果你使用forward&lt;Ts&gt;(ts)...,那么第二个元素的内容将被移动到second。因此,如果ts... 的元素是更复杂的对象,那么您将在命令(std::cout &lt;&lt; ... &lt;&lt; ts); 中访问无效内存
        • 对不起,这是我的错。字符串文字总是左值。
        猜你喜欢
        • 2019-09-16
        • 1970-01-01
        • 2020-09-06
        • 1970-01-01
        • 2012-07-09
        • 1970-01-01
        • 1970-01-01
        • 2015-06-05
        • 1970-01-01
        相关资源
        最近更新 更多