检测成语:为什么条件必须是 `using` 指令?

detection idiom: why does the condition have to be a `using` directive?

编辑:我的其他问题关注于这个问题的简化版本,可能更容易理解。

我写了一个小片段来重现 std::experimental::is_detected (here) 的行为。我的实现基本上取自 cppreference,但我去掉了 Default 模板参数。

我的问题是:在下面的代码片段中,为什么 has_type(要检查的条件)using 声明并且不能是,例如结构(在这种情况下 is_detected returns 是错误的结果)?

/***** is_detected definition *****/
template<typename...Args>
using void_t = void;

template<typename Void, template<class...> class Op, typename ...Args>
struct Detector {
   static constexpr bool value = false;
};

template<template<class ...> class Op, typename ...Args>
struct Detector<void_t<Op<Args...>>, Op, Args...> {
   static constexpr bool value = true;
};

template<template<class...> class Op, typename...Args>
using is_detected_t = Detector<void, Op, Args...>;
/****************************/

/***** is_detected test *****/
// two dummy types on which to test a condition
struct Yes { using type = void; };
struct No { };

// the condition to test
template<typename T>
using has_type = typename T::type;
// struct has_type { using type = typename T::type; }; // does not work as intended!

int main() {
   static_assert(is_detected_t<has_type, Yes>::value, "");
   static_assert(!is_detected_t<has_type, No>::value, "");
   return 0;
}

查看检测器实际如何使用 has_type 可能会有所帮助:

template<template<class ...> class Op, typename ...Args>
struct Detector<void_t<  Op<Args...>>, Op, Args...> {
//                       ^^  ^^
//                 has_type  Yes/No
   static constexpr bool value = true;
};

为了匹配此特化,编译器必须确保 Op<Args...>,在用实际参数(has_typehas_type 替换参数(OpArgs...)时Yes/No),必须命名一个类型(因为这是模板 void_t 要求的第一个模板参数)。

由于 has_type 不是类型,而是某种类型的 别名 ,因此它必须查看别名是否命名为类型。

对于 Yes,这将是 Yes::type,这又是 void 的别名。 void是一个类型,所以一切都很好,专业化匹配,valuetrue

对于 No 这将是 No::type,它不存在(毕竟 No 没有成员 type)。因此,替换失败(但这不是错误,SFINAE),无法使用特化。因此编译器选择基本模板,其中 valuefalse.


现在,当您按如下方式定义 has_type 时会发生什么:

template<typename T>
struct has_type { using type = typename T::type; }

然后上述特化需要(在 No 情况下)类型 has_type<No> 存在。 has_type 是一个 class 模板,其中给定了一些类型(No 是一个类型,所以一切都很好) "produces" 一个类型。因此,has_type<No>是一个类型。因此专业化匹配,valuetrue.

此时不需要has_type<No>成员您甚至可以使用template<typename> struct has_type;(只有声明,没有定义)。也就是说,它可能是一个不完整的类型:

A template argument for a type template parameter must be a type-id, which may name an incomplete type [..]

http://en.cppreference.com/w/cpp/language/template_parameters

这些内容只有在编译器实际需要它们时才重要,例如用于创建该类型的对象:

// Class template with some random members.
template<typename T>
struct Foo {
    using baz = typename T::baz;
    constexpr static int value = T::value * 42;
};

// Class template which is even only declared
template<typename X> struct Bar; // no definition

// Does not use its template parameter in any way. Needs just a type name.
template<typename> struct Defer {};

int main() {
    Defer<Foo<int>> ok;
    Defer<Bar<int>> ok_too;
    // Foo<int> fail;
    // Bar<int> fail_too;
    return 0;
}

这种机制通常用于 "type tags",例如可用于从单个模板创建具有相同 "content" 的不同类型:

template<typename /* TAG */, typename ValueType>
struct value_of_strong_type {
  ValueType value;
  // ...
};

struct A_tag; // no definition
using A = value_of_strong_type<A_tag, int>;
struct B_tag; // no definition
using B = value_of_strong_type<B_tag, int>;

AB 的行为相同,但不能相互转换,因为它们是完全不同的类型。


要使检测器使用您展示的 class 模板,您需要进行以下专业化:

template<template<class ...> class Op, typename ...Args>
struct Detector<void_t<typename Op<Args...>::type>, Op, Args...> {
//                     ^^^^^^^^            ^^^^^^
   static constexpr bool value = true;
};

虽然你不能只添加它,否则你 运行 会陷入模糊的解析错误。