SFINAE 确定类型是否具有潜在的重载方法
SFINAE to determine if a type has a potentially overloaded method
我正在寻找一个 SFINAE 解决方案来在编译时检查一个类型是否有一个方法。我的目标是检查类型是否有效 "duck type",但我想使用 static_assert
来提供信息性消息,而不是无用的编译错误。
我找到了[这个问题],它为我的问题提供了一个相当好的答案,除了当类型为方法提供重载时它会失败:
template<typename...> // parameter pack here
using void_t = void;
template<typename T, typename = void>
struct has_xxx : std::false_type {};
template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};
这对下面的例子很好用,区分方法和成员变量:
struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
struct Foo3{ static double xxx;};
double Foo3::xxx = 42;
int main() {
static_assert(has_xxx<Foo>::value, "");
static_assert(!has_xxx<Foo2>::value, "");
static_assert(!has_xxx<Foo3>::value, "");
}
如果存在过载,代码将失败:
struct Foo { int xxx() {return 0;} void xxx(int){} };
int main() {
static_assert(has_xxx<Foo>::value, "");
}
Failing live demo with overloaded method
如何改进此代码以处理重载?
如果您所做的只是检查 name xxx
是否存在于类型 T
中,那么我们可以采用以下适用的方法所有非final
/union
类型T
。我们可以创建一个从 T
和具有成员 xxx
的后备类型公开继承的新类型。当且仅当 T
没有成员开始时,我们才能在派生类型上明确地访问 xxx
。
template <class T>
class has_xxx_impl
{
private:
struct Fallback {
int xxx;
};
struct Derived : T, Fallback { };
template <class U>
static std::false_type test(decltype(&U::xxx)* );
template <class U>
static std::true_type test(...);
public:
using type = decltype(test<Derived>(nullptr));
};
template <class T>
struct has_xxx : has_xxx_impl<T>::type
{ };
如果您想检查更具体的内容 - 例如 T
,您可以调用 T{}.xxx()
,我会以不同的方式进行。
正确的鸭子类型检查是 "can your xxx
be invoked with a specific signature, and its return value used in a certain context"。重载 xxx
没有用,因为它的使用方式很重要。
我们从can_apply
开始
namespace details {
template<template<class...>class Z, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;
它告诉您给定的参数是否可以合法地传递给某个模板。
然后我们表达我们想要的鸭子类型:
template<class T>
using xxx_result = decltype( std::declval<T>().xxx() );
template<class T>
using can_xxx = can_apply< xxx_result, T >;
和 can_xxx<T>
是真还是假取决于我们是否可以做 t.xxx()
。
如果我们想要类型限制,我们只需:
template<class T, class R>
using xxx_result_as_R = decltype( R(std::declval<T>().xxx()) );
template<class T, class R>
using can_xxx_as_R = can_apply< xxx_result_as_R, T, R >;
所以如果你想要 xxx
到 return 一些 int
-able,我们得到:
template<class T>
using valid_xxx = can_xxx_as_R<T, int>;
我正在寻找一个 SFINAE 解决方案来在编译时检查一个类型是否有一个方法。我的目标是检查类型是否有效 "duck type",但我想使用 static_assert
来提供信息性消息,而不是无用的编译错误。
我找到了[这个问题],它为我的问题提供了一个相当好的答案,除了当类型为方法提供重载时它会失败:
template<typename...> // parameter pack here
using void_t = void;
template<typename T, typename = void>
struct has_xxx : std::false_type {};
template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};
这对下面的例子很好用,区分方法和成员变量:
struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
struct Foo3{ static double xxx;};
double Foo3::xxx = 42;
int main() {
static_assert(has_xxx<Foo>::value, "");
static_assert(!has_xxx<Foo2>::value, "");
static_assert(!has_xxx<Foo3>::value, "");
}
如果存在过载,代码将失败:
struct Foo { int xxx() {return 0;} void xxx(int){} };
int main() {
static_assert(has_xxx<Foo>::value, "");
}
Failing live demo with overloaded method
如何改进此代码以处理重载?
如果您所做的只是检查 name xxx
是否存在于类型 T
中,那么我们可以采用以下适用的方法所有非final
/union
类型T
。我们可以创建一个从 T
和具有成员 xxx
的后备类型公开继承的新类型。当且仅当 T
没有成员开始时,我们才能在派生类型上明确地访问 xxx
。
template <class T>
class has_xxx_impl
{
private:
struct Fallback {
int xxx;
};
struct Derived : T, Fallback { };
template <class U>
static std::false_type test(decltype(&U::xxx)* );
template <class U>
static std::true_type test(...);
public:
using type = decltype(test<Derived>(nullptr));
};
template <class T>
struct has_xxx : has_xxx_impl<T>::type
{ };
如果您想检查更具体的内容 - 例如 T
,您可以调用 T{}.xxx()
,我会以不同的方式进行。
正确的鸭子类型检查是 "can your xxx
be invoked with a specific signature, and its return value used in a certain context"。重载 xxx
没有用,因为它的使用方式很重要。
我们从can_apply
namespace details {
template<template<class...>class Z, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;
它告诉您给定的参数是否可以合法地传递给某个模板。
然后我们表达我们想要的鸭子类型:
template<class T>
using xxx_result = decltype( std::declval<T>().xxx() );
template<class T>
using can_xxx = can_apply< xxx_result, T >;
和 can_xxx<T>
是真还是假取决于我们是否可以做 t.xxx()
。
如果我们想要类型限制,我们只需:
template<class T, class R>
using xxx_result_as_R = decltype( R(std::declval<T>().xxx()) );
template<class T, class R>
using can_xxx_as_R = can_apply< xxx_result_as_R, T, R >;
所以如果你想要 xxx
到 return 一些 int
-able,我们得到:
template<class T>
using valid_xxx = can_xxx_as_R<T, int>;