Selecting by interface, an idea almost to good to be true

published at 27.02.2016 17:36 by Jens Weller

Last night, I've been coding until 3 am. Working on an API which will connect my CMS to JSON libraries in a generic way. For this I did study different JSON Libraries in the past weeks. I almost wrote another one ;) Yet, I had the idea to write a generic interface to interface with some of these libraries, so that my own code is not hard wired to a certain API.

RapidJson has a generic Handler interface, which was the first thing I started with. I don't want to force the interface on the client classes on my site, so I was looking for a way to detect if a method exists or not in a type. Then a little enable if magic should do, and I could implement StartObject etc. in my handler (which is a template) in two ways: one just returning true, and one calling StartObject on the template parameter of my handler.

There is no trait in the standard, but boost.tti offers some traits which allow you do to some introspection on a type. Including if a member function exists or not, boost.tti generates traits based on macros like BOOST_TTI_HAS_MEMBER_FUNCTION. You simply define a macro with the name of the member function you are looking for:

../../files/blog/boostttihasmemberfunction.png

So, this depends not on the type it self. In a generic context, it tells you simply if type T has foo (or Type). This code is mostly declaring traits to look for a type method, which exists in some value classes of JSON Libraries. The idea then is, to use enable if, to simply select the correct implementation method:

../../files/blog/typeinterface.png

Sweet, isn't it? Well, if it would compile. I made a minimal example this morning, and trying many ways, I could not get this to compile. Overload resultion does not like this method, or you get type related template errors. So this Idea seems to be go good to be true...

Getting SFINAE and enable_if into submission

The title says almost. So there is a solution, so far I know this compiles on GCC 4.8,5.1 and Clang (coliru default). Also, there is a little pit fall, but more to this later. First, when using enable_if on the return type, it suddenly starts working, but a little hack is needed, to make it depend on the template parameter of the template class. Here is a working example:

template< typename Interface >
struct GenericInterface
{
template< typename T = Interface > typename std::enable_if< detail::has_member_function_interface< std::string (T::*)()const >::value,std::string >::type query_interface()const { return i.interface(); } template< typename T = Interface > typename std::enable_if< detail::has_member_function_get_my_interface< std::string (T::*)()const >::value, std::string>::type query_interface()const { return i.get_my_interface(); } void print() { std::cout << query_interface() << "\n"; } private: Interface i; };

Just a toy example to test things. The default has_member_function_type<Type,ReturnType> will only detect non const methods without parameters, when looking for method with const qualifier or parameters, the member function pointer like syntax is required. So, this way one can select a template method based on the interface needed to call. No specialization or tags needed.

Update - how to do it in C++11 only

Paul Fultz II pointed out at twitter, that decltype and declval can do the same, simlpy by using auto and return type deduction:

template< struct T = Interface >
auto query_interface() const -> decltype(std::declval<T>().interface())
{
    return i.interface();
}
template< struct T = Interface >
auto query_interface() const -> decltype(std::declval<T>().get_my_interface())
{
    return i.get_my_interface();
}
template< struct T = Interface >
auto query_interface()const -> decltype(std::declval<T>().myInterface(),int())
{
    return i.myInterface();
}

The last type in the list of decltype is the return type, the others query the interface, one of the things I need to detect, if a type has begin() and end() members, in a function returning void, this can simply be done by decltype(arg.begin(),arg.end(),void()). So boost::tti and its boost::mpl dependencies are not needed to do this, even better!