Detecting the Existence of Member Functions at Compile-Time

From Wiki

Jump to: navigation, search

[edit] Introduction

I wrote an article previously on Detecting the Existence of Operators at Compile-Time. This article builds upon similar tricks explained in that article and it is recommended that you have read it first. In this article, a technique is described that allows to determine at compile-time whether a class has a particular static or non-static member function. We're going to use the SFINAE principle to achieve this. In this particular example, the goal is to detect at compile-time whether a given class has a member function called get_int with the function signature int get_int().

[edit] A Full Example

#include <boost/static_assert.hpp>
#include <boost/mpl/bool.hpp>
 
namespace detail
{
    // two types that are guaranteed to have different sizes
    typedef char yes;
    typedef char (&no)[2];
 
    template <typename T, int (T::*f)()>
    struct test_get_int_wrapper {};
 
    // via SFINAE only one of these overloads will be considered
    template <typename T> yes get_int_tester(test_get_int_wrapper<T, &T::get_int>*);
    template <typename T> no get_int_tester(...);
 
    template <typename T>
    struct test_get_int_impl
    {
        static const bool value = sizeof(get_int_tester<T>(0)) == sizeof(yes);
    };
}
 
template <typename T>
struct test_get_int : boost::mpl::bool_<detail::test_get_int_impl<T>::value>
{
};
 
struct A {};
 
struct B 
{
    int get_int();
};
 
int main(int argc, _TCHAR* argv[])
{
    BOOST_STATIC_ASSERT(!test_get_int<A>::value);
    BOOST_STATIC_ASSERT(test_get_int<B>::value);
}

[edit] Explanation

Just like in the previous article on detecting operators, we declare two types called yes and no that are guaranteed to have different sizes. Again, the trick will be to use the sizeof operator on a function whose return type is one of the two types.

typedef char yes;
typedef char (&no)[2];

We have two overloads of a template function called get_int_tester. Because of the SFINAE principle, the first overload is only chosen if the test_get_int_wrapper pointer passed to it compiles. It compiles only if T has a member function called get_int with a matching function signature.

template <typename T, int (T::*f)()>
struct test_get_int_wrapper {};
 
template <typename T> yes get_int_tester(test_get_int_wrapper<T, &T::get_int>*);
template <typename T> no get_int_tester(...);

Now comes the really important bit. The test_get_int_impl template metafunction determines the size of the return type of the get_int_tester function. If the type T has the desired member function the first function overload is chosen and its return type has a size of sizeof(yes) and therefore our template metafunction returns true. Otherwise, the second function overload with the ellipsis is chosen. Its return type has a size of sizeof(no) and our template metafunction returns false.

template <typename T>
struct test_get_int_impl
{
    static const bool value = sizeof(get_int_tester<T>(0)) == sizeof(yes);
};

By replacing the function pointer passed as template argument to test_get_int_wrapper we can test for functions with different signatures, or static member functions:

// test for a static member function: static int get_int()
template <int (*f)()>
struct test_get_int_wrapper {};
 
template <typename T> yes get_int_tester(test_get_int_wrapper<&T::get_int>*);
template <typename T> no get_int_tester(...);

And that's all there is to it. Similar to detecting operators, this technique has problems when applied to a class that declares the tested function private.

Personal tools