std :: tuple是如何实现的?

时间:2010-10-28 09:23:48

标签: c++ c++11 tuples std

我想知道在C ++ 0x的标准库中如何实现元组。我试着阅读description in libstdc++ manual,然后阅读template listing,但很难理解它是如何工作的,特别是在阅读代码时。

有人能用几句话解释我的元组实现的想法吗?我想知道这一点,因为我想在我的代码中使用元组,我想了解它是如何工作的以及它带来了什么类型的开销(仅扩展编译时间,对内存执行许多复制操作,在构造函数中执行许多其他函数)等等。)。

5 个答案:

答案 0 :(得分:25)

实现元组的一种方法是使用多重继承。元组元素由叶类保存,元组类本身继承自多个叶子。在伪代码中:

template<typename T0, typename T1, ..., typename Tn>
class PseudoTuple : TupleLeaf<0, T0>, TupleLeaf<1, T1>, ..., TupleLeaf<n, Tn> {
   ...
};

每个叶子都有一个索引,因此即使它们包含的类型相同,每个基类也会变得唯一,所以我们可以通过简单的static_cast访问 nth 元素:

static_cast<TupleLeaf<0, T0>*>(this);
// ...
static_cast<TupleLeaf<n, Tn>*>(this);

我在这里写了关于这个“扁平”元组实现的详细解释:C++11 tuple implementation details (Part 1)

答案 1 :(得分:10)

元组通常实现为编译时链表。

代码通过模板语法有点混淆,但通常会出现以下元素:

  1. 具有头部和尾部元素的类链(cons-elements)
  2. 一个空尾实例,用于指示列表的结尾。
  3. 将列表移动到某个索引的递归代码,实现为递归模板实例化(在编译时实例化)。
  4. 在C ++ 03中存在合理的实现(例如,boost)。

    Variadic模板允许无限数量的元素,如Motti所述。

    成本通常是编译时间一。复制构造函数可能在初始化期间被调用(最大1),以及复制元组本身时。

答案 2 :(得分:5)

我想我会基于@mitchnull答案添加一个非伪代码的简单实现,以供参考

#include <iostream>

// Contains the actual value for one item in the tuple. The 
// template parameter `i` allows the
// `Get` function to find the value in O(1) time
template<std::size_t i, typename Item>
struct TupleLeaf {
    Item value;
};

// TupleImpl is a proxy for the final class that has an extra 
// template parameter `i`.
template<std::size_t i, typename... Items>
struct TupleImpl;

// Base case: empty tuple
template<std::size_t i>
struct TupleImpl<i>{};

// Recursive specialization
template<std::size_t i, typename HeadItem, typename... TailItems>
struct TupleImpl<i, HeadItem, TailItems...> :
    public TupleLeaf<i, HeadItem>, // This adds a `value` member of type HeadItem
    public TupleImpl<i + 1, TailItems...> // This recurses
    {};

// Obtain a reference to i-th item in a tuple
template<std::size_t i, typename HeadItem, typename... TailItems>
HeadItem& Get(TupleImpl<i, HeadItem, TailItems...>& tuple) {
    // Fully qualified name for the member, to find the right one 
    // (they are all called `value`).
    return tuple.TupleLeaf<i, HeadItem>::value;
}

// Templated alias to avoid having to specify `i = 0`
template<typename... Items>
using Tuple = TupleImpl<0, Items...>;

int main(int argc, char** argv) {
    Tuple<int, float, std::string> tuple;
    Get<0>(tuple) = 5;
    Get<1>(tuple) = 8.3;
    Get<2>(tuple) = "Foo";
    std::cout << Get<0>(tuple) << std::endl;
    std::cout << Get<1>(tuple) << std::endl;
    std::cout << Get<2>(tuple) << std::endl;
    return 0;
}

答案 3 :(得分:3)

可以通过引入核心语言的variadic templates实现std::tuple

我知道这是在乞求这个问题,但它为你提供了一个更好的搜索词。

答案 4 :(得分:1)

通过组合而不是继承使用递归数据结构的实现:

#include <iostream>

template <typename T, typename... Ts>
struct Tuple {
    Tuple(const T& t, const Ts&... ts)
        : value(t)
        , rest(ts...)
    {
    }

    constexpr int size() const { return 1 + rest.size(); }

    T value;
    Tuple<Ts...> rest;
};
template <typename T>
struct Tuple<T> {
    Tuple(const T& t)
        : value(t)
    {
    }

    constexpr int size() const { return 1; }

    T value;
};

template <size_t i, typename T, typename... Ts>
struct nthType : nthType<i-1, Ts...> {
    static_assert(i < sizeof...(Ts) + 1, "index out of bounds");
};

template <typename T, typename... Ts>
struct nthType<0, T, Ts...> { T value; };

template <size_t i>
struct getter {
    template <typename... Ts>
    static decltype(nthType<i, Ts...>::value)& get(Tuple<Ts...>& t) {
        return getter<i-1>::get(t.rest);
    }
};
template <>
struct getter<0> {
    template <typename T, typename... Ts>
    static T& get(Tuple<T, Ts...>& t) {
        return t.value;
    }
};

template <size_t i, typename... Ts>
decltype(nthType<i, Ts...>::value)& get(Tuple<Ts...>& t) {
    return getter<i>::get(t);
}


int main()
{
    Tuple<int,int,float> t(1,2,3.4);
    
    std::cout << get<0>(t) << "\n";
    std::cout << get<1>(t) << "\n";
    std::cout << get<2>(t) << "\n";
    // std::cout << get<3>(t) << "\n"; // error with useful information
    
    return 0;
}

我发现这种方法远远优于替代方法,因为它在执行 apply map 等递归操作时使用起来非常直观,特别是如果您曾经在函数式编程中使用过递归数据结构。当然,对于索引检索,我们需要做一些奇怪的模板事情,但通常使用递归性质是非常直观的。如果有人能解释为什么这种设置不常见,我很想知道。