Checking whether a class has a member function with a given signature

When writing template code, being able to detect whether a type has a method with a given signature may be very useful. For example, when writing a custom buffer, checking for T::size() may allow us to preallocate memory in an input function. Checking for T::operator bool() will tell us whether our type is testable without writing specializations for every such type, which would be especially tedious for unnamed types. (side note: lambdas don’t have operator bool(), but they’re implicitly convertible to function pointers when their capture list is empty, which should make them testable, but they aren’t in VC 10 and 11)

Achieving the desired result requires basic understanding of SFINAE and overload resolution. Let’s say we want to check for existence of ::size() as used in the standard library or in the codebase of Qt, that is size_t (T::*)() const and int (T::*)() const respectively.

The naïve implementation

template<typename T>
struct has_size_method
{
private:
	typedef std::true_type yes;
	typedef std::false_type no;
 
	template<typename U, int (U::*f)() const> struct SFINAE{};
	template<typename U, std::size_t (U::*f)() const> struct SFINAE<U,f>{};
 
 
	template<class C> static yes test(SFINAE<C,&C::size>*);
	template<class C> static no test(...);
 
public:
	static constexpr bool value = std::is_same<yes,decltype(test<T>(nullptr))>::value;
};

Create a metafunction and use SFINAE to disable test returning yes if no match is found. Simple and effective, or is it? Unfortunately not. Consider x and y in the following code:

struct x
{
        int size() const { return 42; }
};
 
struct y : x {};
 
cout << has_size_method<x>::value << ", " << has_size_method<y>::value << endl;

It will print “1, 0”, because SFINAE disables the SFINAE<C,&C::size>* overload if the desired function exists in C‘s parent class. Here’s a full example.

The real deal A partial fix

To avoid the above problem, the implementation can’t use &C::size (or any desired function name) as a template parameter, and the check needs to be split into two parts: checking whether size is a member of T and checking whether it has an acceptable signature.

template<typename T>
struct has_size_method
{
private:
	typedef std::true_type yes;
	typedef std::false_type no;
 
	template<typename C>
	static yes test(int (C::*f)() const){
		return yes{};
	}
 
	template<typename C>
	static yes test(size_t (C::*f)()){
		return yes{};
	}
 
	template<typename C>
	static auto test(decltype(&C::size),void*) -> decltype(test(&C::size)){
		return test(&C::size);
	}
 
	template<typename C>
	static no test(...){
		return no{};
	}
 
public:
 
	static constexpr bool value = std::is_same<decltype(test<T>(0,0)),yes>::value;
};

It works correctly in all most required cases and can be easily modified to account for different signatures. If constexpr was changed to const (as VC11 with CTP doesn’t seem to recognize this keyword) the above code would compile and work correctly on GCC, clang and MSVC11. If you’re bound to support VS11 this is the best you can do. This solution was inspired by Mike Kinghan’s answer on stackoverflow and, even though the it is not perfect, I recommend reading his post there.

The real real deal

After this was posted on reddit, STL pointed out (thanks!) that that there is a vastly superior solution to this problem – Expression SFINAE. It allows not only cleaner code, but also clear stating of the intent by showing the usage (although that may cause some implementation details to leak out if you’re not careful).

template<typename T>
struct has_size_method
{
private:
	typedef std::true_type yes;
	typedef std::false_type no;
 
	template<typename U> static auto test(int) -> decltype(std::declval<U>().size() == 1, yes());
 
	template<typename> static no test(...);
 
public:
 
	static constexpr bool value = std::is_same<decltype(test<T>(0)),yes>::value;
};

The only drawback: it doesn’t work on VC11 (or older).

The Boost way

The upcoming version 1.54 of Boost will contain Boost.TTI library, which offers very similar functionality, but it suffers from the same problem (though with Boost I’m more inclined to say that it’s a design decision) that the first solution does – inherited functions are invisible:

#include <iostream>
#include <iomanip>
#include <type_traits>
#include <sstream>
 
#include <boost/tti/has_member_function.hpp>
 
struct int_size{
	int size() const { return 42; };
};
 
struct derived_int_size : int_size{};
 
struct size_t_size{
	std::size_t size() const { return 42; }
};
 
struct derived_size_t_size : size_t_size{};
 
struct void_size{
	void size() {}
};
 
struct no_size{};
 
struct template_int_size{
	template<typename=int>
	int size() const { return 42; }
};
 
struct overloaded_size{
	size_t size() const { return 42; }
	int size() { return 41; }
};
 
 
BOOST_TTI_HAS_MEMBER_FUNCTION(size)
 
int main()
{
	using namespace std;
 
#define dbg(x) { stringstream s; s << boolalpha << left << setw(8) <<\
	(has_member_function_size<x,size_t,boost::mpl::vector<>, boost::function_types::const_qualified>::value ||\
	has_member_function_size<x,int,boost::mpl::vector<>, boost::function_types::const_qualified>::value)\
	<< #x; cout << s.str() << endl; }
 
	dbg(int_size);
	dbg(derived_int_size);
	dbg(size_t_size);
	dbg(derived_size_t_size);
	dbg(void_size);
	dbg(no_size);
	dbg(template_int_size);
	dbg(overloaded_size);
}

Output

true    int_size
false   derived_int_size
true    size_t_size
false   derived_size_t_size
false   void_size
false   no_size
true    template_int_size
true    overloaded_size

Conclusion

A simple Google search will give plenty of results for this problem, but they are mostly links to the naïve solution (1, 2, 3, 4). The correct way to do it is either to roll your own type trait using Expression SFINAE or use Boost’s solution.

For a description of overload resolution mechanisms, there’s an excellent overview published by ACCU, and if you’re interested in checking how far you can go with it, the fantastic write-up on Flaming Dangerzone is a must read.

7 thoughts on “Checking whether a class has a member function with a given signature

  1. Got pointed to this for reference, just two small improvements:
    – you don’t need “== 1” since the decltype is a valid expression by itself
    – no need to use is_same with the standard boolean types

    So you may change it to:

    template
    struct has_size_method {
    private:
    template
    static constexpr auto test(int) -> decltype(std::declval().size(), std::true_type());

    template
    static constexpr std::false_type test(…);

    public:
    using type = decltype(test(0));
    static constexpr bool value = type::value;
    };

    But slightly more elegant is renaming the above to …_impl, removing the static bool and deriving directly from the boolean types with:

    template
    struct has_size_method : public has_size_method_impl::type {};

    This works because std::true/false::type have::value and you don’t have to worry whether it decays to a bool or something else. And you can stow away the _impl into some detail namespace for nicer headers ;)

    1. I think the “== 1” here is necessary to check the method signature(return type actually), though, not quite sufficient.

    2. You’re right, I’ll update the post. In my defense, I did learn a bit since this has been posted :)

    1. Yeah, I’ve been meaning to fix that for a while, but I’m going to drop wordpress in the future so I didn’t want to spend too much time on it. For the time being, use html entities &​gt; and &​lt; :(

  2. these template class helps testing if the function\methid is on class, but limited, because we can’t choose another function name, but i know that can be updated for that ;)
    but my problem is other: can i test\check if the function was defined?
    i have these macro, but isn’t ANSI :(
    (don’t works on Visual Studio, but works on GNU and, i think, on clang compilers)
    #if defined __GNUC__
    #define EVENT [[gnu::weak]]
    #elif defined __clang__
    #define EVENT [[llvm::weak]]
    #endif

    #define IS_EVENT_DEFINED(sym) (static_cast(sym))

    can anyone advice me?
    thank you for all

Leave a Reply to Ivan Sanz-Carasa Cancel reply

Your email address will not be published.