检测成语:为什么条件必须是 `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_type
和 has_type
替换参数(Op
和 Args...
)时Yes
/No
),必须命名一个类型(因为这是模板 void_t
要求的第一个模板参数)。
由于 has_type
不是类型,而是某种类型的 别名 ,因此它必须查看别名是否命名为类型。
对于 Yes
,这将是 Yes::type
,这又是 void
的别名。 void
是一个类型,所以一切都很好,专业化匹配,value
是true
。
对于 No
这将是 No::type
,它不存在(毕竟 No
没有成员 type
)。因此,替换失败(但这不是错误,SFINAE),无法使用特化。因此编译器选择基本模板,其中 value
是 false
.
现在,当您按如下方式定义 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>
是一个类型。因此专业化匹配,value
是 true
.
此时不需要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>;
A
和 B
的行为相同,但不能相互转换,因为它们是完全不同的类型。
要使检测器使用您展示的 class 模板,您需要进行以下专业化:
template<template<class ...> class Op, typename ...Args>
struct Detector<void_t<typename Op<Args...>::type>, Op, Args...> {
// ^^^^^^^^ ^^^^^^
static constexpr bool value = true;
};
虽然你不能只添加它,否则你 运行 会陷入模糊的解析错误。
编辑:
我写了一个小片段来重现 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_type
和 has_type
替换参数(Op
和 Args...
)时Yes
/No
),必须命名一个类型(因为这是模板 void_t
要求的第一个模板参数)。
由于 has_type
不是类型,而是某种类型的 别名 ,因此它必须查看别名是否命名为类型。
对于 Yes
,这将是 Yes::type
,这又是 void
的别名。 void
是一个类型,所以一切都很好,专业化匹配,value
是true
。
对于 No
这将是 No::type
,它不存在(毕竟 No
没有成员 type
)。因此,替换失败(但这不是错误,SFINAE),无法使用特化。因此编译器选择基本模板,其中 value
是 false
.
现在,当您按如下方式定义 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>
是一个类型。因此专业化匹配,value
是 true
.
此时不需要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>;
A
和 B
的行为相同,但不能相互转换,因为它们是完全不同的类型。
要使检测器使用您展示的 class 模板,您需要进行以下专业化:
template<template<class ...> class Op, typename ...Args>
struct Detector<void_t<typename Op<Args...>::type>, Op, Args...> {
// ^^^^^^^^ ^^^^^^
static constexpr bool value = true;
};
虽然你不能只添加它,否则你 运行 会陷入模糊的解析错误。