Using traits to determine whether a class has a specific member function named “X”

Let’s look at the example first. The code is slightly modified from has_parenthesis_operator in IOD library.

template <typename T>
struct HasClone
{
    template<typename C>
    static char test(int x, decltype(&C::Clone)* = nullptr);
    template<typename C>
    static int test(...);
    static const bool value = sizeof(test<T>(0)) == sizeof(char);
};

This is a small piece of code that defines traits struct which will have true when type T has function named Clone in its public scope. All the interesting things happen in two highlighted lines: 5 and 8. At line 5, I declare a static function takes two arguments: an int and a pointer to a pointer to member function of class C, named Clone. The function’s return type is char. The second function takes any inputs and returns int. Note that the return for two functions differs. The line 8 declares a static const bool variable named value which will be initialized to true if sizeof(test(0)) == sizeof(char).

This code makes use of SFINAE (Substitution Failure Is Not An Error) in a way that if type C has a function named Clone, decltype(&C::Clone) will refer to a pointer to the member function Clone, whereas if type C does not have a function named Clone, decltype(&C::Clone) will refer to unknown-type. When compiler reaches at line 8, it will try to resolve overloading of function test(0). If type T has member of Clone, the first test function – test(int, decltype<&C::Clone>*) matches the call test(0) perfectly, since the second parameter has default value of nullptr. The compiler will prefer this match to the match between test(…) and test(0) since the latter match is more general than the first perfect match, thus evaluating the size of return type of test(0) will result in sizeof(char).

When type T does not have a function named Clone, the compiler cannot generate code for char test(int, unknown-type*) since the second parameter’s type is not specified. Yet, this substitution failure will not result in a compile error, it will just make compiler refuse to create code for templatized function which incurred a substitution failure. Thus, HasClone for T without Clone() will only have int test(…), resulting sizeof(test(0)) to be sizeof(int). This will initialize value to be false.

And here is a macro to save you some key strokes.

#define HAS_MEMBER_FUNC_TRAIT(funcName) \
template <typename T> \
struct Has##funcName \
{ \
    template<typename C> \
    static char test(int x, decltype(&C::funcName)* = nullptr); \
    template<typename C> \
    static int test(...); \
 \
    static const bool value = sizeof(test<T>(0)) == sizeof(char);\
}

//! Declares exact same traits as above
HAS_MEMBER_FUNC_TRAIT(Clone);

EDIT:

Matthieu gave me few advice to enhance HasFunc trait. The first was to get rid of redundant * operator, and the second was that above example might not work for templatized member functions when compiled by compilers other than MSVC. When Clone() is templatized function like below:

class A
{
    template<typename T>
    void Clone();
}

MSVC matches this Clone to C::Clone in line 5, but GCC does not. Since we don’t want compiler dependent output from anything, I added some code that enables HasClone compiled by GCC to find class A’s Clone function.

#define HAS_MEMBER_FUNC_TRAIT(funcName) \
template <typename T> \
struct Has##funcName \
{ \
    template<typename C> \
    static char test(int x, decltype(&C::funcName) = nullptr); \
    template<typename C> \
    static char testTemplate(int x, decltype(&C::template funcName<>) = nullptr); \
    template<typename C> \
    static int test(...); \
    template<typename C> \
    static int testTemplate(...); \
 \
    static const bool value = (sizeof(test<T>(0)) && sizeof(char) && sizeof(testTemplate<T>(0)) != sizeof(char)); \
}

Here, I added a second test overload to test if there is a template member function named funcName. The template keyword is added to notice the compiler that name after this point is indeed with a template specifier, rather than two comparison operators . This implementation rejects a template member function named funcName in MSVC. The reason that I chose to separate template member function from HasFuncName is that as someone adds another template type parameter in a function funcName, you have to add matching test function inside HasFuncName struct for GCC, which is messy and something that I don’t want to do.

EDIT2:

Using explicit specialization of function can circumvent most of cases associated with template instantiation, except one situation where all instantiation is prohibited except specific types. For example, below code will not compile with HasFunc

class B
{
public:
    template<typename T>
    void Clone() { static_assert(false, "Clone cannot be instantiated!"); }
};
template <> void B::Clone<int>() {}

The static_assert will fire when you try to check if Clone is in it by HasClone::value. If you must check on template member function for presence, you’d have to use the version below and provide types that enable template instantiation as variadic arguments to the macro.

#define HAS_MEMBER_FUNC_TRAIT(funcName, ...) \
template <typename T> \
struct Has##funcName \
{ \
    template<typename C> \
    static char test(int x, decltype(&C::funcName) = nullptr); \
    template<typename C> \
    static char testTemplate(int x, decltype(&C::template funcName<__VAR_ARGS__>) = nullptr); \
    template<typename C> \
    static int test(...); \
    template<typename C> \
    static int testTemplate(...); \
 \
    static const bool value = (sizeof(test<T>(0)) && sizeof(char) && sizeof(testTemplate<T>(0)) != sizeof(char)); \
}

Nonetheless, this approach also has pitfalls. If different classes have different set of instantiable types for its template member functions, then you’d have to generate multiple versions with different names of HasFuncName struct! Ha, this is never going to end, and I am going to stop here and move on to other topics now. Thank you for reading!

Advertisements
Using traits to determine whether a class has a specific member function named “X”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s