通用 `Is_enabled` SFINAE 结构
Generic `Is_enabled` SFINAE structure
我的目标是实现一个结构模板,该模板可用于检测模板替换是否格式正确或是否会失败。使用示例是根据模板参数是否可比较,提供两个版本的模板函数。
如果明确地为每个场景提供结构,则可以很容易地解决这个问题,例如模板类型是否存在相等运算符,如图。但是我未能实现一个可以接受(几乎)任意构造作为模板参数的结构。
目前我所达到的 "best" 方法使用模板模板参数。它可以编译,但不适合参数替换应该格式正确的情况。
#include <iostream>
#include <type_traits>
template <typename T = void, typename...>
using Enable = T;
template <bool Cond, typename T = void>
using Enable_if = typename std::enable_if<Cond, T>::type;
template <typename T, template<typename> class X, typename = void>
struct Is_enabled : std::false_type {};
template <typename T, template<typename> class X>
struct Is_enabled<T, X, Enable<X<T>>> : std::true_type {};
/// An example of construct
template <typename T>
using Equals = decltype(std::declval<T>() == std::declval<T>());
template <typename T>
using Enabled_eq = Enable_if<Is_enabled<T, Equals>::value>;
template <typename T>
using Disabled_eq = Enable_if<!Is_enabled<T, Equals>::value>;
template <typename T>
Enabled_eq<T> foo()
{
std::cerr << "enabled!" << std::endl;
}
template <typename T>
Disabled_eq<T> foo()
{
std::cerr << "disabled!" << std::endl;
}
struct A {};
int main(int /*argc*/, const char* /*argv*/[])
{
foo<int>(); /// should print "enabled!"
foo<A>(); /// should print "disabled!"
return 0;
}
如果是int
,显然应该打印"enabled!"
,如果是A
,它应该打印"disabled!"
。但它总是打印 "disabled!"
,所以 Is_enabled
的特化永远不会完成。
我是不是有点接近正确的解决方案,还是会更复杂?
Is_enabled
的第三个模板参数默认为void
。这是编译器将在 Is_enabled<T, Equals>
实例化中使用的内容。也就是说,仅当 Enable<X<T>>
的计算结果为 void
时,才能使用 Is_enabled<T, X, Enable<X<T>>> : std::true_type {};
。通过显式传递模板参数 X<T>
到 class 模板 Enable
声明为:
template <typename T = void, typename...>
using Enable = T;
您实际上为 X<T>
本身创建了一个别名,并且根本没有使用 void
类型(默认类型,调度工作所需的类型)。在您的情况下, X<T>
是 decltype
说明符的结果。对于 foo<A>()
它确实会导致实例化失败。但是,对于 foo<int>()
,您得到的整数比较结果类型是 bool
。也就是说,虽然没有替换失败,但编译器不能使用 class 模板特化,因为它专用于 void
,而不是 bool
。
为了修复代码,您应该重写 Enable
以始终得到 void
:
template <typename...>
using Enable = void;
这也称为 std::void_t
。
我的目标是实现一个结构模板,该模板可用于检测模板替换是否格式正确或是否会失败。使用示例是根据模板参数是否可比较,提供两个版本的模板函数。
如果明确地为每个场景提供结构,则可以很容易地解决这个问题,例如模板类型是否存在相等运算符,如图
目前我所达到的 "best" 方法使用模板模板参数。它可以编译,但不适合参数替换应该格式正确的情况。
#include <iostream>
#include <type_traits>
template <typename T = void, typename...>
using Enable = T;
template <bool Cond, typename T = void>
using Enable_if = typename std::enable_if<Cond, T>::type;
template <typename T, template<typename> class X, typename = void>
struct Is_enabled : std::false_type {};
template <typename T, template<typename> class X>
struct Is_enabled<T, X, Enable<X<T>>> : std::true_type {};
/// An example of construct
template <typename T>
using Equals = decltype(std::declval<T>() == std::declval<T>());
template <typename T>
using Enabled_eq = Enable_if<Is_enabled<T, Equals>::value>;
template <typename T>
using Disabled_eq = Enable_if<!Is_enabled<T, Equals>::value>;
template <typename T>
Enabled_eq<T> foo()
{
std::cerr << "enabled!" << std::endl;
}
template <typename T>
Disabled_eq<T> foo()
{
std::cerr << "disabled!" << std::endl;
}
struct A {};
int main(int /*argc*/, const char* /*argv*/[])
{
foo<int>(); /// should print "enabled!"
foo<A>(); /// should print "disabled!"
return 0;
}
如果是int
,显然应该打印"enabled!"
,如果是A
,它应该打印"disabled!"
。但它总是打印 "disabled!"
,所以 Is_enabled
的特化永远不会完成。
我是不是有点接近正确的解决方案,还是会更复杂?
Is_enabled
的第三个模板参数默认为void
。这是编译器将在 Is_enabled<T, Equals>
实例化中使用的内容。也就是说,仅当 Enable<X<T>>
的计算结果为 void
时,才能使用 Is_enabled<T, X, Enable<X<T>>> : std::true_type {};
。通过显式传递模板参数 X<T>
到 class 模板 Enable
声明为:
template <typename T = void, typename...>
using Enable = T;
您实际上为 X<T>
本身创建了一个别名,并且根本没有使用 void
类型(默认类型,调度工作所需的类型)。在您的情况下, X<T>
是 decltype
说明符的结果。对于 foo<A>()
它确实会导致实例化失败。但是,对于 foo<int>()
,您得到的整数比较结果类型是 bool
。也就是说,虽然没有替换失败,但编译器不能使用 class 模板特化,因为它专用于 void
,而不是 bool
。
为了修复代码,您应该重写 Enable
以始终得到 void
:
template <typename...>
using Enable = void;
这也称为 std::void_t
。