使用 void_t 的多个 SFINAE class 模板专业化

Multiple SFINAE class template specialisations using void_t

多个 class 模板专业化是否有效,当每个模板专业化仅在涉及非推导上下文中的模板参数的模式之间不同时?

std::void_t 的一个常见示例使用它来定义一个特征,该特征揭示类型是否具有名为 "type" 的成员 typedef。在这里,采用单一专业化。这可以扩展为确定一个类型是否具有 一个名为 "type1" 的成员 typedef 或一个名为 "type2" 的成员。下面的 C++1z 代码使用 GCC 编译,但不使用 Clang。合法吗?

template <class, class = std::void_t<>>
struct has_members : std::false_type {};

template <class T>                      
struct has_members<T, std::void_t<typename T::type1>> : std::true_type {};

template <class T>                                                        
struct has_members<T, std::void_t<typename T::type2>> : std::true_type {};

我不认为它是正确的,或者至少,如果我们用嵌套了 type1 和 type2 的类型实例化 has_members,结果将是两个特化

has_members<T, void> 

这将是无效的。在代码被实例化之前我认为没问题,但 clang 很早就拒绝了它。在 g++ 上,您在这个用例中失败,一旦实例化:

struct X
{
    using type1 = int;
    using type2 = double;
};

int main() {
    has_members<X>::value;
}

错误消息似乎没有描述实际问题,但至少发出了:

<source>:20:21: error: incomplete type 'has_members<X>' used in nested name specifier
     has_members<X>::value;
                     ^~~~~

如果您使用只有 type1 type2 而不是两者的类型实例化它, 然后 g++ 干净地编译它。所以它反对成员都存在的事实,导致模板的实例化冲突。

要获得析取,我认为您需要这样的代码:

template <class, class = std::void_t<>>
struct has_members : std::bool_constant<false> {};

template <class T>
struct has_members<T, std::enable_if_t<
        std::disjunction<has_member_type1<T>, has_member_type2<T>>::value>> : 
    std::bool_constant<true> {};

这假设您具有确定 has_member_type1 和 has_member_type2 已经写入的特征。

有一条规则,部分专业化必须比主要模板更专业化 - 您的两个专业化都遵循该规则。但是没有一条规则规定部分特化永远不会模棱两可。更重要的是 - 如果实例化导致模棱两可的专业化,则程序格式错误。但是必须首先发生模棱两可的实例化!

看来 clang 正在遭受 CWG 1558 的困扰,并且过于急于用 void 替换 std::void_t

这几乎是CWG 1980

In an example like

template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();

it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i.e., equivalent but not functionally equivalent.

如果使用 void_t 的非别名实现:

template <class... Ts> struct make_void { using type = void; };
template <class... Ts> using void_t = typename make_void<Ts...>::type;

然后 clang 允许两种不同的专业化。当然,在同时具有 type1type2 类型定义错误的类型上实例化 has_members,但这是预料之中的。