Here is a short, tidy and documented way of doing what you are attempting,
with some possible bugs addressed thereafter.
#include <type_traits>
/*
Template `has_mf_foo_accepts_int_returns_int<T>`
has a static boolean public member `value` that == true
if and only if `T` is a class type that has a public
member function or member function overload
`int T::foo(ArgType) [const]` where `ArgType`
is a type to which `int` is implicitly convertible.
*/
template <typename T>
struct has_mf_foo_accepts_int_returns_int {
/* SFINAE success:
We know now here `int *` is convertible to
"pointer to return-type of T::foo(0)"
*/
template<typename A>
static constexpr bool test(
decltype(std::declval<A>().foo(0)) *prt) {
/* Yes, but is the return-type of `T::foo(0)`
actually *the same* as `int`?...
*/
return std::is_same<int *,decltype(prt)>::value;
}
// SFINAE failure :(
template <typename A>
static constexpr bool test(...) {
return false;
}
/* SFINAE probe.
Can we convert `(int *)nullptr to
"pointer to the return type of T::foo(0)"?
*/
static const bool value = test<T>(static_cast<int *>(nullptr));
};
template<typename T>
struct mystruct
{
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
T val;
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` == `int` and `has_good_foo` == true.
*/
template<typename R = int>
typename std::enable_if<
(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
return val.foo(i);
}
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` != `int` or `has_good_foo` != true.
*/
template<typename R = int>
typename std::enable_if<
!(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
static_assert(has_good_foo::value && std::is_same<R,int>::value,
"mystruct<T> does not implement someMethod(R)");
return i;
}
};
// Testing...
#include <iostream>
struct with_foo_int
{
int foo(int i) {
return i + 1;
}
};
using namespace std;
int main(void)
{
mystruct<with_foo_int> ms1;
cout << ms1.someMethod(41) << endl;
mystruct<double> ms2;
cout << ms2.someMethod(41) << endl; // static_assert failure
return 0;
}
This solution faithfully reproduces a couple of possible loopholes in your
own attempt as posted:-
1) It looks as if you may believe that evaluating std::declval<U>().foo(0)
is
a SFINAE way of determining whether U::foo
exists and takes a single argument
of type int
. It doesn't. It is merely a SFINAE way of determining whether
U::foo(ArgType)
exists where ArgType
is anything to which 0
is
implicitly convertible. Thus ArgType
could be any pointer-or-arithmetic
type, not just int
.
2) You may not have considered that std::declval<U>().foo(0)
will be satisfied
if either or both of U::foo(ArgType)
U::foo(ArgType) const
exists. You
may well care whether you call a const
or a non-const
member function on
U
, and you would certainly care which of two member function you call. If
with_foo_int
were defined as:
struct with_foo_int
{
int foo(int i) const {
return i + 1;
}
int foo(int i) {
return i + 2;
}
};
then the solution given would call the non-const
overload and
ms1.someMethod(41)
would == 43
.
2) Is easily dealt with. If you wish to ensure that you can only call
T::foo(ArgType) const
then add a const
qualifier to mystruct::someMethod
.
If you don't care or wish only to call T::foo(ArgType)
then leave things
as they are.
1) is a little harder to solve, because you must craft a SNIFAE probe for
T::foo
that is satisfied only if it has the right signature, and that
signature will either be const
qualified or not. Let's assume you want
int T::foo(int) const
. In that case, replace template
has_mf_foo_accepts_int_returns_int
with:
/* Template `has_mf_foo_arg_int_returns_int<T>
has a static boolean public member `value` that == true
if and only if `T` is a class type that has an un-overloaded
a public member `int T::foo(int) const`.
*/
template< typename T>
struct has_mf_foo_arg_int_returns_int
{
/* SFINAE foo-has-correct-sig :) */
template<typename A>
static std::true_type test(int (A::*)(int) const) {
return std::true_type();
}
/* SFINAE foo-exists :) */
template <typename A>
static decltype(test(&A::foo))
test(decltype(&A::foo),void *) {
/* foo exists. What about sig? */
typedef decltype(test(&A::foo)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
and in template mystruct
replace:
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
with:
using has_good_foo = has_mf_foo_arg_int_returns_int<T>;
(Template has_mf_foo_arg_int_returns_int
is adapted
from my other answer and
you can read how it works there.)
What you gain in SFINAE-precision from the latter approach comes at
a price. The approach requires you to attempt to take the address of T::foo
,
to see if it exists. But C++ will not give you the address of an overloaded
member function, so this approach will fail if T::foo
is overloaded.
The code here will compile (or appropriately static_assert
) with
GCC >= 4.7.2 clang >= 3.2.