Using SFINAE to detect a member function

nijansen picture nijansen · Sep 2, 2013 · Viewed 12.6k times · Source

In C++11, to find out whether a class has a member function size, you could define the following test helper:

template <typename T>
struct has_size_fn
{
    typedef char (& yes)[1];
    typedef char (& no)[2];

    template <typename C> static yes check(decltype(&C::size));
    template <typename> static no check(...);

    static bool const value = sizeof(check<T>(0)) == sizeof(yes);
};

Is there a similar trick for doing this in C++98 without relying on compiler extensions such as typeof?

Answer

Matthieu M. picture Matthieu M. · Sep 2, 2013

Actually, your detection is potentially erroneous.

The problem is that all you are detecting is that C has a member size:

  • it could be an attribute
  • it could be a method with whatever signature
  • there could even be several methods (with various signatures)

If you wish to harden the detection, you should attempt to detect only the right size (for whatever right is). Here is such a hardened detection.

template <typename T>
class has_size {
private:
  typedef char Yes;
  typedef Yes No[2];

  template <typename U, U> struct really_has;

  template <typename C> static Yes& Test(really_has <size_t (C::*)() const,
                                        &C::size>*);

  // EDIT: and you can detect one of several overloads... by overloading :)
  template <typename C> static Yes& Test(really_has <size_t (C::*)(),
                                        &C::size>*);

  template <typename> static No& Test(...);

public:
    static bool const value = sizeof(Test<T>(0)) == sizeof(Yes);
};

Edit: with overloads.

The trick to deal with incorrect size members is the really_has structure. I make no pretense that it is perfect, though...

In C++11, things are simpler (though no less verbose) because you can detect things by use directly. The equivalent trait is thus:

template <typename T>
class has_size {
private:
  typedef char Yes;
  typedef Yes No[2];

  template<typename C> static auto Test(void*)
    -> decltype(size_t{std::declval<C const>().size()}, Yes{});

  template<typename> static No& Test(...);

public:
    static bool const value = sizeof(Test<T>(0)) == sizeof(Yes);
};

However, the recommended method in C++ is not to use traits if you can; in functions for example you can use decltype right in the type signature.