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.
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 ;)
I think the “== 1” here is necessary to check the method signature(return type actually), though, not quite sufficient.
original:
decltype(declval().size() == 1, yes())
the proper comma-operator-based implementation (ignores return type of .size()) should be:
decltype(void(declval().size()), declval())
but the 100% proper implementation should be:
enable_if_t<is_same_v<decltype(declval().size()), size_t>, yes>
For more info about the comma operator decltype case check:
http://blog.codeisc.com/2018/01/09/cpp-comma-operator-nice-usages.html
You’re right, I’ll update the post. In my defense, I did learn a bit since this has been posted :)
those comments are removing my comments with TEMPLATES because is detecting them as possible XSS attack and HTML tags
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 > and < :(
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