C ++模板元编程:使用类型别名与继承的不同行为

时间:2014-10-11 03:13:53

标签: c++ template-meta-programming c++14

我试图反转c ++ 14 std::index_sequence并遇到使用继承的原始实现的问题。我找到了使用本地类型别名的解决方法,但我想了解为什么原始代码不起作用。

使用继承破坏反向

这是我第一次尝试撤销std::index_sequence

/// Helper class that appends an element onto an index_sequence.
/// Base case.
template<size_t, typename>
struct Append : std::index_sequence<> { };

template<size_t X, size_t... XS>
struct Append<X, std::index_sequence<XS...>> : std::index_sequence<XS..., X> { };

/// Reverse elements of a std::index_sequence
template<typename>
struct Reverse;

/// Base case
template<>
struct Reverse<std::index_sequence<>> : std::index_sequence<> { };

template<size_t X, size_t... XS>
struct Reverse<std::index_sequence<X, XS...>> : Append<X, Reverse<std::index_sequence<XS...>>> { };

此支持功能打印std::index_sequence

的内容
template <size_t... XS>
void print_seq(std::index_sequence<XS...>)
{
    std::cout << "size " << sizeof...(XS) << ": ";
    bool Do[] = { (std::cout << XS << " ", true)... };
    (void) Do;
    std::cout << std::endl;
}

遗憾的是,这种实施并没有像我预期的那样发挥作用:

print_seq(Reverse<std::make_index_sequence<10>>{});

输出显示大小为0且没有元素:

size 0: 

使用类型别名反向工作

然后我稍微修改了我的原始代码,使用类型别名而不是内在。所有其他逻辑应与第一个示例完全相同。

template<size_t, typename>
struct AppendUsingType {
    using type = std::index_sequence<>;
};

template<size_t X, size_t... XS>
struct AppendUsingType<X, std::index_sequence<XS...>> {
    using type = std::index_sequence<XS..., X> ;
};

template<typename>
struct ReverseUsingType;

template<>
struct ReverseUsingType<std::index_sequence<>> {
    using type = std::index_sequence<>;
};

template<size_t X, size_t... XS>
struct ReverseUsingType<std::index_sequence<X, XS...>> {
    using type = typename AppendUsingType<X, typename ReverseUsingType<std::index_sequence<XS...>>::type>::type;
};

现在,当我检查类型时:

print_seq(typename ReverseUsingType<std::make_index_sequence<10>>::type{});

我看到正确的输出:

 size 10: 9 8 7 6 5 4 3 2 1 0 

问题

即使我找到了解决方案,我也很想理解为什么使用继承的实现失败而使用类型别名的实现按预期运行。我在gcc和Clang都看到了这一点,所以我怀疑语言规范中有一些原因。

(或许相关问题:typedef vs public inheritance in c++ meta-programming

2 个答案:

答案 0 :(得分:6)

首先,使用模板参数推导完成部分特化的匹配。如果可以从提供的参数推导出部分特化的模板参数,则部分特化被认为是匹配的并且可以使用。

对于函数调用,标准允许从派生类推导基类模板参数中有一个特殊规则,否则如果否则会导致失败(§14.8.2.1[temp.deduct.call] / p4);此规则不适用于其他情况,特别是它不适用于部分专业化匹配。

因此,编译器无法将Reverse<std::index_sequence<XS...>>std::index_sequence<XS...>匹配,Append的部分特化不可行,而是使用主模板。


使用无类型别名的反转的可能实现:

template<typename, typename=std::index_sequence<>>
struct Reverse;

template<size_t...ints>
struct Reverse<std::index_sequence<>,
               std::index_sequence<ints...>> : std::index_sequence<ints...>{};

template<size_t first, size_t...remaining, size_t...done>
struct Reverse<std::index_sequence<first, remaining...>, std::index_sequence<done...>>
    : Reverse<std::index_sequence<remaining...>, std::index_sequence<first, done...>> {};

答案 1 :(得分:2)

感谢T.C.提供了很好的解释和更优雅的逆转实施。

如果有人需要将反转概括为任何类型的std::integer_sequence,这里是代码T.C的一个小更新。发布了那样做:

template<typename T, typename=std::integer_sequence<typename T::value_type>>
struct Reverse;

template<typename T, T... list>
struct Reverse<std::integer_sequence<T>, std::integer_sequence<T, list...>> :
    std::integer_sequence<T, list...>
{ };

template<typename T, T first, T... rest, T... done>
struct Reverse<std::integer_sequence<T, first, rest...>, std::integer_sequence<T, done...>> :
    Reverse<std::integer_sequence<T, rest...>, std::integer_sequence<T, first, done...>>
{ };