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
时,始终会选择第一个模板?
答案 0 :(得分:4)
这是否意味着“一个模板参数”总是在“两个模板参数,一个默认值”之前被选中?
没有。当实例化与其模板参数列表匹配时,使用部分特化,并且当它比任何其他匹配的部分特化项“更专业化”时(这里没有相关性,因为没有声明其他部分特化)。
当您将模板实例化为IsIterable<Foo>
时,将使用默认模板参数,因此实例化为IsIterable<Foo, void>
。如果Foo
有begin()
和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上的一次演讲中很好地解释了这项技术。如果你有两个小时的稀疏,我强烈建议你观看他的演讲录音: