问题
I was looking for an SFINAE solution to check at compile time if a type has a method. My goal is to check if a type is a valid "duck type", but instead of a useless compile error, I want to use static_assert
to provide an informative message.
I found [this question], which provides a fairly good answer to my problem, except it fails when the type provides overload to the method:
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)>{};
This works fine with the following example, and differentiate method and member variable:
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, "");
}
Original live demo
The code fails if there is an overload:
struct Foo { int xxx() {return 0;} void xxx(int){} };
int main() {
static_assert(has_xxx<Foo>::value, "");
}
Failing live demo with overloaded method
How can this code be improved to handle overloading?
回答1:
A proper duck type check is "can your xxx
be invoked with a specific signature, and its return value used in a certain context". Having an overloaded xxx
is not useful, because it matters how it is used.
We start with 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...>;
which tells you if given arguments can be legally passed to some template.
We then express the duck type we want:
template<class T>
using xxx_result = decltype( std::declval<T>().xxx() );
template<class T>
using can_xxx = can_apply< xxx_result, T >;
and can_xxx<T>
is truthy or falsy depending on if we can do a t.xxx()
or not.
If we want a type restriction, we just:
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 >;
so if you want xxx
to return something int
-able, we get:
template<class T>
using valid_xxx = can_xxx_as_R<T, int>;
回答2:
If all you're doing is checking if the name xxx
exists within type T
, then we can have the following approach that works for all non-final
/union
types T
. We can create a new type that inherits publicly from both T
and a fallback type that has a member xxx
. We can access xxx
unambiguously on the derived type if and only if T
didn't have the member to begin with.
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
{ };
If you want to check for more specific things - like for a T
, you can call T{}.xxx()
, I would do it differently.
来源:https://stackoverflow.com/questions/39750160/sfinae-to-determine-if-a-type-has-a-potentially-overloaded-method