10

I asked a question about a week ago inquiring how I would be able to simply instantiate a class template only if the type it took had a specific member function. In my answer I got sort of a complicated solution. But then I tried to do it on my own. I just wanted to know if this enough to figure out of a given type T has a void function named f taking 0 parameters.

#include <type_traits>
#include <utility>

template <typename T, typename = void>
struct has_f : std::false_type { };

template <typename T>
struct has_f<
    T,
    decltype(std::declval<T>().f(), void())> : std::true_type { };

template <typename T, typename = typename std::enable_if<has_f<T>::value>::type>
struct A { };

struct B
{
    void f();
};

struct C { };

template class A<B>; // compiles
template class A<C>; // error: no type named ‘type’ 
                     // in ‘struct std::enable_if<false, void>’

If so, why are the other answers so complicated in this thread?

Community
  • 1
  • 1
Me myself and I
  • 3,990
  • 1
  • 23
  • 47
  • 2
    Yes, this is SFINAE and yes, this is correct. As for why the other answers are more complicated... hard to tell. – Andy Prowl Jun 15 '13 at 14:11
  • To clarify `has_f` itself relies on SFINAE, `A` doesn't. – Luc Danton Jun 16 '13 at 00:18
  • As a possibility, SFINAE support in some compilers (*cough* MSVC2012) sort of sucks, so some of that complexity might be workarounds to have a more portable SFINAE. As an aside, `std::declval().f()` and `std::declval().f()` (or is it `std::declval().f()`?) can be different things in C++1y due to the rvalue reference to `this` feature. – Yakk - Adam Nevraumont Jun 16 '13 at 06:16

1 Answers1

7

Yep, you have solved it in the simplest, most idiomatic style of C++11 SFINAE.

Note that you didn't check that the return type is void, that it's a nonstatic member, nor that there are no parameters. f is simply callable with no arguments. It could even be a functor.

To check for a nullary member nonstatic function returning void, use

template <typename T>
struct has_f<T, decltype(void( static_cast< void (T::*)( void ) >( &T::f ) )) >
    : std::true_type {};

template <typename T>
struct has_f<T, decltype(void( static_cast< void (T::*)( void ) const >( &T::f ) )) >
    : std::true_type {};
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • So I use `std::result_of` to check if the return type is void? – Me myself and I Jun 15 '13 at 14:26
  • @MemyselfandI That's one way, you could also use `decltype`. `std::result_of` does a bit of a strange dance to support the INVOKE concept used by `std::bind` and `std::function`. See my edit for a quick way to check several conditions at once. In any case, you'll also need `std::is_same`. – Potatoswatter Jun 15 '13 at 14:29
  • I think you meant `std::is_same< decltype( &T::f ), void (T::*)( void )>::value` (moved closing parenthesis), and similarly for the line below. –  Jun 15 '13 at 14:36
  • @MemyselfandI `std::result_of` is not required by the Standard to be SFINAE-friendly. Recent enough implementations do try to make it so however (as otherwise it makes the trait awkward to use). – Luc Danton Jun 15 '13 at 17:44
  • @LucDanton It's not required by the ratified C++11 draft, but it was added shortly after as a C++1y feature. Either way it doesn't really give `decltype` a run for its money, unless you're using the special pointer-to-member cases of INVOKE. – Potatoswatter Jun 16 '13 at 00:14
  • Using `decltype(&T::f)` fails miserably when `f` is overloaded, but I'm pretty sure the code in the question handles that correctly. – Ben Voigt Jun 16 '13 at 00:31
  • @BenVoigt Bah, need `static_cast` instead. It's hairy. – Potatoswatter Jun 16 '13 at 03:01