我正在编写一个模板,该模板定义在类型参数包中赋予它的类型,并且其位置与传递给模板的数字相同。这是我写的:

template<size_t I, typename T, typename... Ts>
struct get_type {
    typedef typename std::conditional<I == 0, T, typename get_type<I-1, Ts...>::type>::type type;
};

template<size_t I, typename T>
struct get_type<I, T> {
    // This works only if compiler stops unfolding the template when I == 0
    static_assert(I == 0, "get_type - index out of bounds");
    typedef T type;
};


如果我们编写这样的代码,这种方法就会出现问题:

static_assert(std::is_same<double, get_type<1,int,double,float>::type>::value, "I wanted double!");


编译器仍然将模板“展开”到末尾(即使直到那时,确切地说,当I等于0时,它仍应知道类型),最后,I溢出并且不再等于0,这意味着static_assert会引发错误,即索引I超出范围。但是,如果I超出范围,我仍然想在编译期间抛出错误。有什么办法吗?

最佳答案

编译器必须展开模板,否则它将不知道type的类型。

如果索引超出范围,std::tuple_element_t已经给出了(相当冗长的)错误。

template<size_t I, typename... Ts>
using get_type_t = std::tuple_element_t<I, std::tuple<Ts...>>;


通过与明确的边界检查配合使用,可以产生更直观的错误消息:

template<size_t I, typename... Ts>
struct get_type {
    using L=std::tuple<Ts...>;
    static_assert(I < 0 || I >= std::tuple_size<L>(), "out of bounds");
    using type = std::tuple_element_t<I, L>;
};

template<size_t I, typename... Ts>
using get_type_t = typename get_type<I, Ts...>::type;


这是一个没有std::tuple开销的例子(改编自boostcon):

struct empty{};

template<class T>
struct tag_t:empty{};

template<class T>
tag_t<T> tag{};

template <typename ignore>
struct lookup;

template <std::size_t... ignore>
struct lookup<std::index_sequence<ignore...>> {
    template <typename nth>
    static nth
    apply(decltype(ignore, empty())..., tag_t<nth>, ...);
};

template<std::size_t I, class... Ts>
using get_type = decltype(
    lookup<std::make_index_sequence<I>>::apply(tag<Ts>...)
);

// Test
static_assert(std::is_same<get_type<1, int, float, int>, float>(), "");
static_assert(std::is_same<get_type<0, int, float, int>, int>(), "");

07-24 09:44
查看更多