Here's a simple class that passes your tests (and doesn't require a dozen of specializations :) ). It also works when foo
is overloaded. The signature that you wish to check can also be a template parameter (that's a good thing, right?).
#include <type_traits>
template <typename T>
struct is_foo {
template<typename U>
static auto check(int) ->
decltype( static_cast< void (U::*)() const >(&U::foo), std::true_type() );
// ^^^^^^^^^^^^^^^^^^^
// the desired signature goes here
template<typename>
static std::false_type check(...);
static constexpr bool value = decltype(check<T>(0))::value;
};
Live example here.
EDIT :
We have two overloads of check
. Both can take a integer literal as a parameter and because the second one has an ellipsis in parameter list it'll never be the best viable in overload resolution when both overloads are viable (elipsis-conversion-sequence is worse than any other conversion sequence). This lets us unambiguously initialize the value
member of the trait class later.
The second overload is only selected when the first one is discarded from overload set. That happens when template argument substitution fails and is not an error (SFINAE).
It's the funky expression on the left side of comma operator inside decltype
that makes it happen. It can be ill-formed when
the sub-expression
&U::foo
is ill-formed, which can happen whenU
is not a class type, orU::foo
is inaccesible, or- there is no
U::foo
the resulting member pointer cannot be
static_cast
to the target type
Note that looking up &U::foo
doesn't fail when U::foo
itself would be ambiguous. This is guaranteed in certain context listed in C++ standard under 13.4
(Address of overloaded function, [over.over]). One such context is explicit type conversion (static_cast
in this case).
The expression also makes use of the fact that T B::*
is convertible to T D::*
where D
is a class derived from B
(but not the other way around). This way there's no need for deducing the class type like in iavr's answer.
value
member is then initialized with value
of either true_type
or false_type
.
There's a potential problem with this solution, though. Consider:
struct X {
void foo() const;
};
struct Y : X {
int foo(); // hides X::foo
};
Now is_foo<Y>::value
will give false
, because name lookup for foo
will stop when it encounters Y::foo
. If that's not your desired behaviour, consider passing the class in which you wish to perform lookup as a template parameter of is_foo
and use it in place of &U::foo
.
Hope that helps.