Testing a C++ Class for a Method
A few weeks ago I was writing a cpp template and I wanted to change the behavior of the template based on the presence of a method on an argument. I suspected it could be done using substitution failure is not an error (SFINAE). I spent sometime searching online for a solution and I found several, but they were all somewhat lacking. Most of the implementations required a consumer of the api to understand SFINAE. They were also not as specific as I wanted. Many would fail if the template parameter contained a method with the same name, but the method took different parameters. After a night of work I was able produce a macro that was easy to use and capable of verifying the method had the expected signature.
My primary motivator for template trickery is to provide an optional requirement in a template interface. For example: You can always test for the presence of an element by performing a linear search on a container. But it might also implement a container.containsElement(element) method that takes advantage of the container’s internal structure to produce a more efficient implementation. Using the macro I wrote it is easy to implement a containsElement(container, element) function that uses the efficient implementation if it is present or falls back on a linear search if it is not.
//Produces a test method in the current namespace.
GENERATE_HAS_METHOD(containsElement)
// Function that provides the implementation if the container has
// the method. The new std::true/std::false_type types are used to provide
// convenient method overriding.
template<class Container, class Element>
bool containsElement(const Container & container, const Element & element,
std::true_type)
{
return container.containsElement(element);
}
// Fallback implementation that uses a linear search.
template<class Container, class Element>
bool containsElement(const Container & container, const Element & element,
std::false_type)
{
auto iter = std::find(container.begin(),container.end(),element);
return iter != container.end();
}
//Function that is used to provide a consistent interface
template<class Container, class Element>
bool containsElement(const Container & container, const Element & element)
{
auto hasMethod = HasMethod::containsElement<Container,Element>();
return containsElement(container, element, hasMethod);
}
//Example of the function being used
if (containsElement(container, value)) {
}
If you are just interested in the macro, then you can jump down to the end of the article. If you want to know how it works then I recommend you read on. Testing for the presence of a method with a specific signature is relatively easy in c++11. The basic idea is you produce two template functions with the same name and compatible overloads. The function with the higher binding priority returns std::true_type and only compiles if the template argument contains the method in question. The second template function returns std::false_type and is used as an overload if the first function fails to compile.
template <...>
std::true_type HasFoo() {
return std::true;
}
template <...>
std::false_type HasFoo() {
return std::false;
}
if (HasFoo<ClassName>(...)) {
}
We can use type coercion to produce two functions that accept the same parameter, but have different binding priorities. We specify a parameter type of int in the std::true_type function and float in the std::false_type function. We then attempt to call the function with 0 as an argument. 0 is a valid floating point number, but the compiler will first try to use it as an integer argument. This means the std::true_type function will be used so long as it is a valid function.
template <...>
std::true_type HasFoo(int) {
return std::true;
}
template <...>
std::false_type HasFoo(float) {
return std::false;
}
if (HasFoo<ClassName>(0)) {
}
Now that we have the compiler trying to use the std::true_type function we need to make it an invalid function if the supplied type does not have the method we are testing for. This can be done using the new decltype keyword provided by c++11. The decltype keyword can be used to determine the type of an arbitrary expression. The catch is the expression needs to be valid. In our case decltype(*(T*)NULL).Foo()) is valid cpp if and only if Foo is a method on type T. The (*(T*) NULL) expression is used to produce a null T pointer and dereference it for an object of type T. Normally this would cause a segfault, but it is safe to do in a decltype expression because the code is never actually executed. The next sample is valid and working cpp. It suffers from one big limitation. It won’t work if Foo takes any arguments.
#include <type_traits>
template <typename T>
auto HasFoo(int) -> decltype((*(T*)NULL).Foo(),std::true_type()) {
return std::true_type();
}
template <typename T>
std::false_type HasFoo(float) {
return std::false_type();
}
//Checks if ClassName has a method Foo();
if (HasFoo<ClassName>(0)) {
}
Checking for a specific set of argument types is easy. You can use the (*(Type*) NULL) trick to produce arguments for the given types and pass them to the method in the declytype declaration. It can be generalized using the parameter pack template feature from c+11. The Parameter pack allows a c++11 variadic template to apply an operation to an arbitrary number of parameter types and then forward them to a function or method call. In our case we will apply the (*(Type*) NULL) trick to the parameter pack in order to try to pass the specified arguments to the method.
#include <type_traits>
template <typename T, typename ... ArgTypes>
auto HasFoo(int) -> decltype((*(T*)NULL).Foo((*(ArgTypes*)NULL)...),std::true_type()) {
return std::true_type();
}
template <typename ... T>
std::false_type HasFoo(float) {
return std::false_type();
}
//Checks if ClassName has a method Foo(FooType arg1, BarType arg2);
if (HasFoo<ClassName,FooType,BarType>(0)) {
}
There are two remaining challenges with the template function. It does not allow for changing the name of the method and it requires we pass 0 to the test function. We can fix the name problem by wrapping the template construct into a macro. The required argument can be fixed by adding an additional function that wraps the call.
The finished macro contains two minor touchups to make it easier to use and more flexible. The constexpr keyword is added to the all of the function declarations. The constexpr keyword can only be applied to functions that do not require any mutation. It allows the function to be used in expressions that resolve during compile time. A namespace is wrapped around the function declarations in order to create a cleaner interface. The finished macro demoed at the start of the article is provided below.
#include <type_traits>
#define GENERATE_HAS_METHOD(MethodName) \
namespace HasMethod { \
template <typename T, typename ... ArgTypes> \
constexpr auto MethodName(int) -> \
decltype((*(T*)NULL).MethodName((*(ArgTypes*)NULL)...),std::true_type()) { \
return std::true_type(); \
} \
template <typename ... T> \
constexpr const std::false_type MethodName(float) { \
return std::false_type(); \
} \
template <typename ... T> \
constexpr auto MethodName() -> \
decltype(HasMethod::MethodName<T...>(0)) { \
return MethodName<T ...>(0); \
} \
}
GENERATE_HAS_METHOD(Foo)
//Checks if ClassName has a method Foo(FooType arg1, BarType arg2);
if (HasMethod::Foo<ClassName,FooType,BarType>()) {
}