【问题标题】:std::tuple_element need deep template instantinationstd::tuple_element 需要深度模板实例化
【发布时间】:2013-09-03 13:02:10
【问题描述】:

在这里http://en.cppreference.com/w/cpp/utility/tuple/tuple_element 给出了可能的 std::tuple_element 实现。

 template< std::size_t I, class T >
struct tuple_element;

// recursive case
template< std::size_t I, class Head, class... Tail >
struct tuple_element<I, std::tuple<Head, Tail...>>
    : std::tuple_element<I-1, std::tuple<Tail...>> { };

// base case
template< class Head, class... Tail >
struct tuple_element<0, std::tuple<Head, Tail...>> {
   typedef Head type;
};

但是,如果元组有很多参数(超过 100 或 200 个参数),则此实现需要深度递归实例化。

Q1:为什么 C++11 没有添加特殊运算符来按索引获取元素? 像 tuple[2] 还是 tuple[0] ?

Q2:是否可以减少深度实例化?例如,在 D 语言中,更多的模板算法(在 typetuple 中)需要 O(log(N) ) 深度实例化。

编辑:Q1:为什么 C++11 没有添加特殊运算符来按索引从可变参数模板中获取元素? 像模板 struct index{ typedef T[3] third_element;}

【问题讨论】:

  • "超过 100 或 200 个参数" 我很确定没有编译器支持具有这么多参数的元组。
  • @NicolBolas 我的 g++4.8.1 编译通过索引技巧创建的 300 元素元组; clang++3.2.1 段错误介于 150 到 200 个参数之间(可能也是索引技巧,谁知道呢)。不过,必须提高两者的模板实例化深度。
  • @DyP:很公平。尽管正如您所指出的,这并不完全是默认情况。
  • 嗯,我确信有一个随机访问参数包的建议,但我找不到它。这回答了为什么它不存在的问题:因为没有人提出它。
  • @SebastianRedl:在邮件列表中有关于它的讨论,但是如果没有某种编译时“静态 for”构造,就没有有效的方法来使用这样的特性。即使constexpr 也不起作用,因为pack[0] 可以具有与pack[1] 不同的类型。用作索引的任何内容都必须是常量表达式。而且没有办法用一个常量表达式的索引来循环一些东西。

标签: c++11 stdtuple


【解决方案1】:

我认为这个实现有 O(log(N)) 实例化深度;感谢 Xeo 的 O(log(N)) 索引技巧(修改为使用 std::size_t 而不是 unsigned)。

编辑:我意识到有一种不同的、更简单且可能更快(编译时间)的解决方案来获得第 n 种类型的元组。

// from https://stackoverflow.com/a/13073076
// indices trick in O(log(N)) instantiations, by Xeo

    // using aliases for cleaner syntax
    template<class T> using Invoke = typename T::type;

    template<std::size_t...> struct seq{ using type = seq; };

    template<class S1, class S2> struct concat;

    template<std::size_t... I1, std::size_t... I2>
    struct concat<seq<I1...>, seq<I2...>>
      : seq<I1..., (sizeof...(I1)+I2)...>{};

    template<class S1, class S2>
    using Concat = Invoke<concat<S1, S2>>;

    template<std::size_t N> struct gen_seq;
    template<std::size_t N> using GenSeq = Invoke<gen_seq<N>>;

    template<std::size_t N>
    struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};

    template<> struct gen_seq<0> : seq<>{};
    template<> struct gen_seq<1> : seq<0>{};

实现/类似于std::tuple_element:

namespace detail
{
    template<std::size_t>
    struct Any
    {
        Any(...) {}
    };

    template<typename T>
    struct wrapper { using type = T; };

    template<std::size_t... Is>
    struct get_nth_helper
    {
        template<typename T>
        static auto deduce(Any<Is>..., wrapper<T>, ...) -> wrapper<T>;
    };

    template<std::size_t... Is, typename... Ts>
    auto deduce_seq(seq<Is...>, wrapper<Ts>... pp)
    -> decltype( get_nth_helper<Is...>::deduce(pp...) );
}

#include <tuple>

template<std::size_t n, class Tuple>
struct tuple_element;

template<std::size_t n, class... Ts>
struct tuple_element<n, std::tuple<Ts...>>
{
    using wrapped_type = decltype( detail::deduce_seq(gen_seq<n>{},
                                                      detail::wrapper<Ts>()...) );
    using type = typename wrapped_type::type;
};

使用示例:

#include <typeinfo>
#include <iostream>

int main()
{
    std::tuple<int, double, bool, char> t;
    tuple_element<1, decltype(t)>::type x;
    std::cout << typeid(x).name() << std::endl;
}

感谢 @Barry 在此答案的早期版本中指出函数/数组类型的问题,并提供修复。


原版: (注:此版本已简化,不添加 cv-qualifiers。)

#include <tuple>


namespace detail
{
    template < std::size_t Index, class Arg >
    struct s_get_one
    {
        // declare a function that links an Index with an Arg type
        friend Arg get(s_get_one, std::integral_constant<std::size_t, Index>);
    };

    template < typename... Bases >
    struct s_get : Bases... {};
}

template < std::size_t I, class T >
struct tuple_element;

template < std::size_t I, class... Args >
struct tuple_element < I, std::tuple<Args...> >
{
    template<class T>
    struct wrapper { using type = T; };

    // deduce indices from seq helper
    template < std::size_t... Is >
    static auto helper(seq<Is...>)
        -> detail::s_get< detail::s_get_one<Is, wrapper<Args>>... >;

    // generate indices in O(log(N)) and use name lookup to find the type
    using IC = std::integral_constant<std::size_t, I>;
    using wrapped_type = decltype( get(helper(gen_seq<sizeof...(Args)>{}), IC{}) );
    using type = typename wrapped_type::type;
};

【讨论】:

  • @KhurshidNormuradov 试试新版本,它可能需要更少的资源。
  • +1,但如果元组包含数组或函数类型,这将不起作用。 get_nth_helper::deduce 应该返回wrapper&lt;T&gt;,然后tuple_element 可以拉出T
  • @Barry 在两个版本中都已修复,谢谢。您如何看待 Kurshid 下面的方法?
  • @dyp 聪明!我会发表与您相同的评论(即设为base&lt;id&lt;(j==i), T&gt;...&gt;)。我想知道这与来自here 的多重继承方法相比如何(搜索_at_index
  • @Barry See ldionne.com/2015/11/29/efficient-parameter-pack-indexing 我认为我的第一个版本类似于 ldionne 的 void* 实现,尽管我猜想 ldionne 的实现更轻量级。
【解决方案2】:

为什么 C++11 没有添加特殊运算符来按索引获取元素?像 tuple2 或 tuple[0] ?

首先,因为即使他们这样做了,它仍然会以相同的方式工作:使用递归。元组主要是一个特性。尽管它们捎带了可变参数模板等语言特性,但它们在 C++98/03 中或多或少具有功能性。

第二,那不可能。并非没有非常困难的语言更改。

不清楚tuple[2]是什么意思。

如果您的意思是 std::tuple&lt;int, float, std::string&gt;[2] 应该以某种方式解析为类型名称 std::string,那么这意味着您现在需要解释为什么它会起作用。同样,元组是 library 功能,而不是语言结构。所以必须有一些语言结构,typename[integer] 是一个有效的结构。那会是什么,这意味着什么?

如果你的意思是给定的:

std::tuple<int, float, std::string> tpl{...};

我们应该能够得到带有tpl[2] 的字符串,这是几个“不会发生”的阴影。 C++ 是一种静态类型语言。 std::get 能够逃脱它所做的唯一原因是整数索引不是函数参数。它是一个 template 参数。这就是允许std::get&lt;0&gt; 返回与std::get&lt;2&gt; 完全不同的类型的原因。 operator[](int) 不会发生这种情况;该函数必须总是返回相同的类型。

所以现在你需要像template&lt;class T, int i&gt; ... operator[]() 这样的东西。这会非常令人困惑,因为您不能再对该类型执行tpl[runtimeValue](因为模板参数必须是编译时值)。没有这样的类型,operator[] 被限制在运行时值上工作。所以你会创建一个非常古怪的类型。

即使那样......它仍然必须进行递归才能获得价值。

是否可以减少深度实例化?

在编译时间之外(这不是一个不合理的问题),这有什么关系?一个体面的内联会丢弃其中的大部分。

至于编译时间,有non-recursive implementations 的各种功能std::tuple。他们是否可以非递归地执行tuple_element,我不这么认为。 This libc++ implementation 似乎暗示 it can't,尽管以非递归方式实现 tuple 本身。

【讨论】:

    【解决方案3】:
        template< int ...i> struct seq{};
    
       // GCC couldn't optimize sizeof..(i) , 
       //see http://stackoverflow.com/questions/19783205/why-sizeof-t-so-slow-implement-c14-make-index-sequence-without-sizeof
       //so I use direct variable `s` instead of it.
       // i.e.  s == number of variadic arguments in `I`.
        template< int s, typename I, typename J > struct concate;
    
        template< int s, int ...i, int ...j>
        struct concate<s, seq<i...>, seq<j...> >
        { 
            typedef seq<i..., (s  + j)...> type;
        };
    
        template<int n> struct make_seq_impl;
        template< int n> using make_seq = typename make_seq_impl<n>::type;
    
        template<> struct make_seq_impl<0>{ typedef seq<> type;};
        template<> struct make_seq_impl<1>{ typedef seq<0> type;};
    
        template<int n> struct make_seq_impl: concate< n/2, make_seq<n/2>, make_seq<n-n/2>>{};
    
        template< typename ...T> using seq_for = make_seq< sizeof...(T) > ;
    
    //----------------------------------
    template< int i, typename T> struct id{};
    template< typename T> struct id<0,T>{ typedef T type;};
    template< typename ...T> struct base : T ... {};
    
    template< typename ...T> struct tuple{};
    
    template< std::size_t i, typename Tuple> struct tuple_element;
    
    template< std::size_t i, typename ...T>
    struct tuple_element< i, tuple<T...> >
    {
          template< typename Seq > struct apply;
          template< int ...j > struct apply< seq<j...> >
          {
             // j xor i ==> ( 0 xor i), (1 xor i), (2 xor i ),...(i xor i) ...
             //    =>  i0, i1, ..., 0 (at pos i) ...
             // and only id<0,T> has `type`.
              typedef base< id< (j xor i), T> ... > base_t;
              typedef typename base_t::type type;
           };
    
         typedef typename apply< seq_for<T...> >::type type;
    };
    

    【讨论】:

    • 我认为您也可以使用template&lt;bool b, typename&gt; struct id{};,专注于b == true,然后将xor 替换为j == i。顺便说一句:非常好的解决方案!
    • 感谢您改进解决方案。真的,如果我使用 bool 而不是 int - 这将是更好的解决方案。
    猜你喜欢
    • 2014-06-16
    • 2019-02-11
    • 2014-05-13
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-22
    相关资源
    最近更新 更多