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

时间:2015-01-10 14:12:50

标签: c++ templates c++11 template-meta-programming

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是一个类模板,第二个参数带有默认模板参数。它显然适用于所有类型。

第二部分是IsIterable的部分模板专精。感谢SFINAE,只有当T具有成员函数begin()end()时才会选择它。

这是我实际提出的第一个问题:基本模板有一个默认参数,所以这意味着它仍然有两个模板参数。部分特化只有一个模板参数。这是否意味着&#34;一个模板参数&#34;总是在之前选择&#34;两个模板参数,一个默认&#34;?

另外,我是否正确理解第一个&#34; base&#34;模板继承自false_type,部分模板专业化&#34;添加&#34;另一个继承级别? (部分特化继承层次结构如下所示:false_type&gt; true_type&gt; 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时,始终会选择第一个模板?

2 个答案:

答案 0 :(得分:4)

  

这是否意味着“一个模板参数”总是在“两个模板参数,一个默认值”之前被选中?

没有。当实例化与其模板参数列表匹配时,使用部分特化,并且当它比任何其他匹配的部分特化项“更专业化”时(这里没有相关性,因为没有声明其他部分特化)。

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

  

另外,我是否正确理解第一个“基础”模板是否继承自false_type,而部分模板专门化“添加”另一个继承级别?

没有。主模板和专业化之间没有隐式继承关系。 (如果你停止称它为“基础”模板并用它的正确名称(主模板)调用它,也许你不会认为存在任何类型的“基础”类关系。)

当使用部分特化(或显式特化)时,它使用代替主模板,而不是除此之外。

  

为什么decltype表达式必须求值为void?

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

  

为什么当我将部分特化从void更改为bool时,总是选择第一个模板?

因为当您编写使用默认模板参数的IsIterable<Foo>时,所以相当于IsIterable<Foo, void>,它永远不会匹配表单IsIterable<T, bool>的部分特化,因为void是与bool不同的类型!

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

答案 1 :(得分:3)

  

第二部分是IsIterable的部分模板专精。感谢SFINAE,只有当T具有成员函数begin()end()时才会选择它。

那不是全部。 t.begin(), t.end(), void()类型t的{​​{1}}表达式也必须具有有效类型,并且必须为T才能选择专门化。 (那是void达到的目标。)

  

这是我实际提出的第一个问题:基本模板有一个默认参数,所以这意味着它仍然有两个模板参数。部分特化只有一个模板参数。那么这是否意味着“一个模板参数”总是在“两个模板参数,一个默认值”之前被选择?

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

  

另外,我是否正确理解第一个“基础”模板是否继承自decltype,而部分模板专门化“添加”另一个继承级别? (部分特化继承层次结构如下所示:false_type&gt; false_type&gt; true_type,其中IsIterable的定义被false_type隐藏? )

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

如果要查看此实时,请尝试将大型非静态数据成员(例如true_type)添加到常规案例中,然后比较int[100]实例化类型。如果特殊情况较小,则不可能从一般情况中得出。

  

现在谈谈我的实际问题。为什么sizeof表达式必须评估为decltype

它不是必须的,但为了使这项工作,您还必须将基本案例的默认值从void更改为void或其他任何内容。但请注意,如果您选择bool以外的任何内容,那么过载的operator,可能会给您带来奇怪的惊喜。

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