部分专业化以及对std :: void_t <>的需求

时间:2018-10-18 00:11:31

标签: c++ language-lawyer partial-specialization

一位语言律师。...

我正在与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”,但我认为这与编译器无关。)

1 个答案:

答案 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 answerthis answer对某些相关问题的详细说明使我正确。

是的,假设这只是一个字,我无法拼写可构造。这是使用标准库的另一个很好的理由。