11

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?

nikolas
  • 8,707
  • 9
  • 50
  • 70
  • That'll detect data members, too. – jrok Sep 02 '13 at 09:54
  • 1
    This might be useful: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector -- _Edit:_ also: http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence (don't stop at the first answer) – gx_ Sep 02 '13 at 09:57
  • @jrok That's ok, it'll be used in a template function that will then cause a compilation error if it's not a function - I can live with that. – nikolas Sep 02 '13 at 10:46
  • @nijansen It'll also trigger a hard error if `size` is private instead of SFINAE. You'll have to live with that too in C++98, unless you switch to the technique from gx_'s comment. – jrok Sep 02 '13 at 12:22

1 Answers1

10

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Unfortunately I'm bound to C++98 here; can I allow both `size_t size()` and `size_t size() const` as signature somehow? – nikolas Sep 02 '13 at 14:37
  • @nijansen: you can, but does it make sense ? Note that if your function is `void doSomething(C const& container)` then it is required to use a `const` method :) . See the edited answer for how to. – Matthieu M. Sep 02 '13 at 14:44
  • Yeah, I've had to change that into passing it by value, because I have to use some libraries that don't really like const correctness ... I still want my `has_size` metafunction to be compatible with these library containers and the STL containers – nikolas Sep 02 '13 at 14:46
  • @nijansen: ouch, passing by value is though, really. You realize it implies a copy of the container ? You might be better off providing a proxy `template class Proxy { Proxy(T& t): _t(t) {} size_t size() const { return _t.size(); } ... };` and then `Proxy proxy(container); doSomething(proxy);` – Matthieu M. Sep 02 '13 at 14:50
  • You're right - that's a brilliant idea. Thanks – nikolas Sep 02 '13 at 14:58