在C ++中,当运行时知道元素索引时,是否可以获取元组的一个元素的类型?

时间:2011-11-07 09:00:13

标签: c++ metaprogramming c++11

typedef std::tuple< int, double > Tuple;
Tuple t;
int a = std::get<0>(t);
double b = std::get<1>(t);

for( size_t i = 0; i < std::tuple_size<Tuple>::value; i++ ) {
   std::tuple_element<i,Tuple>::type v = std::get<i>(t);// will not compile because i must be known at compile time
}

我知道可以为get std::get编写代码(例如参见iterate over tuple),是否可以让std::tuple_element工作?

一些限制(可以放宽):

没有可变参数模板,没有Boost

4 个答案:

答案 0 :(得分:7)

C ++是一种编译时键入的语言。您不能拥有C ++编译器在编译时无法确定的类型。

您可以使用各种形式的多态来解决这个问题。但是在一天结束时,每个变量都必须具有明确定义的类型。因此,虽然您可以使用Boost.Fusion算法迭代元组中的变量,但是您不能有一个循环,其中循环的每次执行可能使用与最后一个不同的类型。

Boost.Fusion可以逃脱它的唯一原因是因为它不使用循环。它使用模板递归来“迭代”每个元素并调用用户提供的函数。

答案 1 :(得分:4)

如果你想不加强,iterate over tuple的答案已经告诉你需要知道的一切。您必须编写一个编译时for_each循环(未经测试)。

template<class Tuple, class Func, size_t i>
void foreach(Tuple& t, Func fn) {
    // i is defined at compile-time, so you can write:
    std::tuple_element<i, Tuple> te = std::get<i>(t);
    fn(te);
    foreach<i-1>(t, fn);
}

template<class Tuple, class Func>
void foreach<0>(Tuple& t, Func fn) { // template specialization
    fn(std::get<0>(t)); // no further recursion
}

并使用它:

struct SomeFunctionObject {
    void operator()( int i ) const {}
    void operator()( double f ) const {}
};

foreach<std::tuple_size<Tuple>::value>(t, SomeFunctionObject());

但是,如果你想迭代一个元组的成员,Boost.Fusion真的是要走的路。

#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>

并在你的代码中写:

boost::for_each(t, SomeFunctionObject());

这是boost :: tuple的一个例子。 boost :: fusion有一个适配器可以在这里使用std :: tuple:http://groups.google.com/group/boost-list/browse_thread/thread/77622e41af1366af/

答案 2 :(得分:1)

不,这不可能像你描述的那样。基本上,您必须为i的每个可能的运行时值编写代码,然后使用一些调度逻辑(例如switch(i))来根据实际的运行时值运行正确的代码。 i

在实践中,可能可以使用模板为i的不同值生成代码,但我不确定如何执行此操作,以及它是否实用。你所描述的听起来像一个有缺陷的设计。

答案 3 :(得分:1)

这是我的元组foreach / transformation函数:

#include <cstddef>
#include <tuple>
#include <type_traits>

template<size_t N>
struct tuple_foreach_impl {
    template<typename T, typename C>
    static inline auto call(T&& t, C&& c)
        -> decltype(::std::tuple_cat(
            tuple_foreach_impl<N-1>::call(
                ::std::forward<T>(t), ::std::forward<C>(c)
            ),
            ::std::make_tuple(c(::std::get<N-1>(::std::forward<T>(t))))
        ))
    {
        return ::std::tuple_cat(
            tuple_foreach_impl<N-1>::call(
                ::std::forward<T>(t), ::std::forward<C>(c)
            ),
            ::std::make_tuple(c(::std::get<N-1>(::std::forward<T>(t))))
        );
    }
};

template<>
struct tuple_foreach_impl<0> {
    template<typename T, typename C>
    static inline ::std::tuple<> call(T&&, C&&) { return ::std::tuple<>(); }
};

template<typename T, typename C>
auto tuple_foreach(T&& t, C&& c)
    -> decltype(tuple_foreach_impl<
        ::std::tuple_size<typename ::std::decay<T>::type
    >::value>::call(std::forward<T>(t), ::std::forward<C>(c)))
{
    return tuple_foreach_impl<
        ::std::tuple_size<typename ::std::decay<T>::type>::value
    >::call(::std::forward<T>(t), ::std::forward<C>(c));
}

示例用法使用以下实用程序来允许将元组打印到ostream:

#include <cstddef>
#include <ostream>
#include <tuple>
#include <type_traits>

template<size_t N>
struct tuple_print_impl {
    template<typename S, typename T>
    static inline void print(S& s, T&& t) {
        tuple_print_impl<N-1>::print(s, ::std::forward<T>(t));
        if (N > 1) { s << ',' << ' '; }
        s << ::std::get<N-1>(::std::forward<T>(t));
    }
};

template<>
struct tuple_print_impl<0> {
    template<typename S, typename T>
    static inline void print(S&, T&&) {}
};

template<typename S, typename T>
void tuple_print(S& s, T&& t) {
    s << '(';
    tuple_print_impl<
        ::std::tuple_size<typename ::std::decay<T>::type>::value
    >::print(s, ::std::forward<T>(t));
    s << ')';
}

template<typename C, typename... T>
::std::basic_ostream<C>& operator<<(
    ::std::basic_ostream<C>& s, ::std::tuple<T...> const& t
) {
    tuple_print(s, t);
    return s;
}

最后,这是示例用法:

#include <iostream>

using namespace std;

struct inc {
    template<typename T>
    T operator()(T const& val) { return val+1; }
};

int main() {
    // will print out "(7, 4.2, z)"
    cout << tuple_foreach(make_tuple(6, 3.2, 'y'), inc()) << endl;
    return 0;
}

请注意,构造可调用对象,以便在需要时可以保持状态。例如,您可以使用以下命令查找元组中可以动态转换为T的最后一个对象:

template<typename T>
struct find_by_type {
    find() : result(nullptr) {}
    T* result;
    template<typename U>
    bool operator()(U& val) {
        auto tmp = dynamic_cast<T*>(&val);
        auto ret = tmp != nullptr;
        if (ret) { result = tmp; }
        return ret;
    }
};

请注意,这样做的一个缺点是它要求callable返回一个值。但是,重写它以检测返回类型对于给定输入类型是否为空,然后跳过结果元组的元素并不困难。更简单的是,您可以完全删除返回值聚合内容,只需将foreach调用用作元组修饰符。

编辑: 我刚刚意识到使用foreach函数可以简单地编写元组写入器(我的元组打印代码比foreach代码长得多)。

template<typename T>
struct tuple_print {
    print(T& s) : _first(true), _s(&s) {}
    template<typename U>
    bool operator()(U const& val) {
        if (_first) { _first = false; } else { (*_s) << ',' << ' '; }
        (*_s) << val;
        return false;
    }
private:
    bool _first;
    T* _s;
};

template<typename C, typename... T>
::std::basic_ostream<C> & operator<<(
    ::std::basic_ostream<C>& s, ::std::tuple<T...> const& t
) {
    s << '(';
    tuple_foreach(t, tuple_print< ::std::basic_ostream<C>>(s));
    s << ')';
    return s;
}