一位语言律师。...
我正在与SFINAE和TMP一起玩,试图加深了解。
考虑以下代码,这是std :: is_default_constructible的幼稚实现
#include <type_traits>
template <typename T, typename = void> struct is_default_constructable : std::false_type {};
template <typename T> struct is_default_constructable<T, decltype(T()) > : std::true_type {};
class NC { NC(int); }; // Not default constructable
#include <iostream>
int main(int, char **)
{
std::cout << "int is_default_constructible? " << is_default_constructable<int>() << std::endl;
std::cout << "NC is_default_constructible? " << is_default_constructable<NC>() << std::endl;
}
这可以正常编译,但实际上不起作用,它对所有类型都返回false。
对于NC情况,这是我所期望的,T()
的格式不正确,因此由于SFINAE而放弃了专业化,并使用了主模板(false_type)。但是对于int
情况,我希望使用decltype(T())
的专业化是有效的,并且等效于T
。
如果我根据<type_traits>
中的实际代码将专业化更改为
template <typename T> using wrap = void;
template <typename T> struct is_default_constructable<T, wrap<decltype(T())> > : std::true_type {};
(即,将第二个模板参数包装在std::void_t<>
的模型中,这会强制第二个类型为void
),
更奇怪的是,使用除void
以外的类型作为主模板或wrap<>
中的默认类型的该方案的变体也失败了,除非这两种类型相同。
有人可以解释为什么wrap<>
的类型和第二个模板参数默认类型必须相同才能选择专业化吗?
(我在g ++ 6.3版中使用“ g ++ -Wall --std = c ++ 17”,但我认为这与编译器无关。)
答案 0 :(得分:1)
这不是SFINAE或部分专业化排序的结果,而是由于使用了默认模板参数。非正式地,原因是默认模板参数的应用发生在 搜索模板定义之前,
。因此,在上述情况下,说is_default_constructable<int>
的代码实际上是在应用默认的第二个参数后请求实例化模板is_default_constructable<int, void>
的。然后考虑可能的定义。
“主要”模板定义明确匹配并包含在内。 给定的局部专业化
template <typename T> struct is_default_constructable<T, decltype(T()) > : std::true_type {};
实际上定义的is_default_constructable<int, int>
与请求的is_default_constructable<int, void>
不匹配,因此即使替换成功,也会忽略专业化。
这样就将主要定义(继承false_type)作为唯一可行的定义,因此选择了它。
当专业化具有wrap<>
(或std::void_t<>
)将第二个arg强制为void
时,专业化将定义与请求匹配的is_default_constructable<int, void>
。该定义(假设替换成功,即T()
的格式)比主要定义(根据用于订购专业化的超级复杂规则)更专业,因此选择它。
顺便说一句,当T是引用类型或其他特殊情况时,上述幼稚的实现可能无法按预期方式工作,这是使用所有标准库版本的充分理由。他们的标准委员会成员比我聪明得多,并且已经想到了所有这些事情。
This answer和this answer对某些相关问题的详细说明使我正确。
是的,假设这只是一个字,我无法拼写可构造。这是使用标准库的另一个很好的理由。