这个模板元编程片段到底发生了什么?

What exactly happens in this template metaprogramming snippet?

template <typename T, typename = void>
struct IsIterable : std::false_type {};

template <typename T>
struct IsIterable<
        T,
        decltype(
            std::begin(std::declval<std::add_lvalue_reference_t<T>>()),
            std::end(std::declval<std::add_lvalue_reference_t<T>>()),
            void()
        )
> : std::true_type {};

以为我明白了。

第一个 IsIterable 是一个 class 模板,第二个参数具有默认模板参数。它显然适用于所有类型。

第二部分是 IsIterable 的部分模板特化。感谢 SFINAE,它仅在 T 具有成员函数 begin()end().

时才被选择

这是我刚刚想到的第一个问题:基本模板有一个默认参数,所以这意味着它仍然有两个模板参数。偏特化只有一个模板参数。那么这是否意味着 "one template parameter" 总是在 之前被选中 "two template parameters, one default"?

此外,第一个 "base" 模板继承自 false_type,部分模板特化 "adds" 另一个继承级别,我的理解是否正确? (部分特化继承层次结构也是这样的:false_type > true_type > IsIterable,其中 false_type 的定义被 true_type 隐藏了吗?)

现在进入我的实际问题。为什么 decltype 表达式的计算结果必须为 void?我以为没关系,我可以写

template <typename T>
struct IsIterable<
        T,
        decltype(
            std::begin(std::declval<std::add_lvalue_reference_t<T>>()),
            std::end(std::declval<std::add_lvalue_reference_t<T>>()),
            bool() // **** change here ****
        )
> : std::true_type {};

还有。但这使得 IsIterable 的值总是 false!为什么当我将偏特化从 void 更改为 bool 时,总是选择第一个模板?

The second part is a partial template specialization of IsIterable. Thanks to SFINAE, it is only chosen when T has the member functions begin() and end().

这还不是全部。 T 类型的 t 的表达式 t.begin(), t.end(), void() 也必须具有有效类型,并且必须是 void 才能选择专业化。 (这就是 decltype 的成就。)

Here is my first question I just came up with actually: the base template has a default argument, so this means it still has two template parameters. The partial specialization only has one template parameter. So does this mean that “one template parameter” always gets choosen before “two template parameters, one default”?

专业化的每个有效实例化也是基本情况的有效实例化(这是一种暗示),因此专业化(如果可行)是更好的匹配。

Also, do I understand it correctly that the first “base” template inherits from false_type, and the partial template specialization “adds” another inheritance level? (so does the partial specializations inheritance hierarchy look like this: false_type > true_type > IsIterable, where the definitions of false_type are hidden by true_type?)

不,它们是完全不相关的类型。 class 模板的特化不会隐式继承一般情况。

如果您想实时查看,请尝试将大型非静态数据成员(例如 int[100])添加到一般情况,然后比较 sizeof 实例化类型。如果特例较小,则不可能从一般情况推导出来。

Now onto my actual question. Why does the decltype expression have to evaluate to void?

它不是必须的,但为了使这项工作有效,您还必须将基本情况的默认值从 void 更改为 bool 或其他。但是请注意,如果您选择 void.

以外的任何内容,超载的 operator, 可能会给您带来奇怪的惊喜

Walter Brown 在 CppCon 2014 的演讲中很好地解释了这项技术。如果你有两个小时的时间,我强烈建议你观看他的演讲录音:

So does this mean that "one template parameter" always gets choosen before "two template parameters, one default"?

没有。当实例化与其模板参数列表匹配时,以及当它 "more specialized" 比匹配的任何其他部分特化时使用部分特化(此处不相关,因为没有声明其他部分特化)。

当您将模板实例化为 IsIterable<Foo> 时,将使用默认模板参数,因此实例化为 IsIterable<Foo, void>。如果 Foobegin()end() 成员,那么 IsIterable<Foo, void> 匹配部分特化,它比主模板更特化。当 Foo 没有 begin()end() 成员时,偏特化不可用,因为当 Foo 被替换时,表达式 decltype(std::declval<T>().begin(), std::declval<T>().end(), void()) 会导致替换失败T.

的位置

Also, do I understand it correctly that the first "base" template inherits from false_type, and the partial template specialization "adds" another inheritance level?

没有。主模板和特化之间没有隐式继承关系。 (如果你不再称它为 "base" 模板,而是用它的专有名称,主模板来称呼它,也许你不会认为有任何类型的 "base" class 关系。)

当使用部分特化(或显式特化)时,它被用来而不是主模板,而不是在主模板之外。

Why does the decltype expression have to evaluate to void?

因为这是主模板上默认模板参数的类型。您不应该为第二个模板参数提供参数,它应该使用默认值,即 void.

Why is, when I change the partial specialization from void to bool, always the first template chosen?

因为当您编写使用默认模板参数的 IsIterable<Foo> 时,所以等同于 IsIterable<Foo, void>,它永远无法匹配 IsIterable<T, bool> 形式的偏特化,因为 voidbool!

的类型不同

如果您使用 bool() 代替部分特化,则您必须编写 IsIterable<Foo, bool> 以使部分特化匹配。你 可以 那样做......但这不是设计使用特征的方式。或者,您也可以将主模板上的默认模板参数更改为 bool,但 void 是惯用的选择,因为具体类型无关紧要,重要的是默认匹配专业化。您应该只提供一个模板参数,而让默认值用于第二个。并且部分特化只有在其第二个模板参数与默认模板参数相同时才能匹配。