Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated check for the existence of a class member function?

Is it possible to write a template that changes behavior depending on if a certain member function is defined on a class?

Here's a simple example of what I would want to write:

template<class T> std::string optionalToString(T* obj) {     if (FUNCTION_EXISTS(T->toString))         return obj->toString();     else         return "toString not defined"; } 

So, if class T has toString() defined, then it uses it; otherwise, it doesn't. The magical part that I don't know how to do is the "FUNCTION_EXISTS" part.

like image 834
andy Avatar asked Nov 02 '08 20:11

andy


People also ask

What is a templated function?

Function templates. Function templates are special functions that can operate with generic types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type. In C++ this can be achieved using template parameters.

What is a templated class C++?

Definition. As per the standard definition, a template class in C++ is a class that allows the programmer to operate with generic data types. This allows the class to be used on many different data types as per the requirements without the need of being re-written for each type.

Can a virtual function be templated?

You cannot have a virtual template function because as far as the compiler is concerned they are two completedly different functions; as their implicit this parameter is of different type.

Which function can access the members of the class even it is defined out of the scope of the class?

The friend function can be a member of another class or a function that is outside the scope of the class. A friend function can be declared in the private or public part of a class without changing its meaning.


2 Answers

Yes, with SFINAE you can check if a given class does provide a certain method. Here's the working code:

#include <iostream>  struct Hello {     int helloworld() { return 0; } };  struct Generic {};      // SFINAE test template <typename T> class has_helloworld {     typedef char one;     struct two { char x[2]; };      template <typename C> static one test( decltype(&C::helloworld) ) ;     template <typename C> static two test(...);      public:     enum { value = sizeof(test<T>(0)) == sizeof(char) }; };      int main(int argc, char *argv[]) {     std::cout << has_helloworld<Hello>::value << std::endl;     std::cout << has_helloworld<Generic>::value << std::endl;     return 0; } 

I've just tested it with Linux and gcc 4.1/4.3. I don't know if it's portable to other platforms running different compilers.

like image 101
Nicola Bonelli Avatar answered Sep 18 '22 05:09

Nicola Bonelli


This question is old, but with C++11 we got a new way to check for a functions existence (or existence of any non-type member, really), relying on SFINAE again:

template<class T> auto serialize_imp(std::ostream& os, T const& obj, int)     -> decltype(os << obj, void()) {   os << obj; }  template<class T> auto serialize_imp(std::ostream& os, T const& obj, long)     -> decltype(obj.stream(os), void()) {   obj.stream(os); }  template<class T> auto serialize(std::ostream& os, T const& obj)     -> decltype(serialize_imp(os, obj, 0), void()) {   serialize_imp(os, obj, 0); } 

Now onto some explanations. First thing, I use expression SFINAE to exclude the serialize(_imp) functions from overload resolution, if the first expression inside decltype isn't valid (aka, the function doesn't exist).

The void() is used to make the return type of all those functions void.

The 0 argument is used to prefer the os << obj overload if both are available (literal 0 is of type int and as such the first overload is a better match).


Now, you probably want a trait to check if a function exists. Luckily, it's easy to write that. Note, though, that you need to write a trait yourself for every different function name you might want.

#include <type_traits>  template<class> struct sfinae_true : std::true_type{};  namespace detail{   template<class T, class A0>   static auto test_stream(int)       -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;   template<class, class A0>   static auto test_stream(long) -> std::false_type; } // detail::  template<class T, class Arg> struct has_stream : decltype(detail::test_stream<T, Arg>(0)){}; 

Live example.

And on to explanations. First, sfinae_true is a helper type, and it basically amounts to the same as writing decltype(void(std::declval<T>().stream(a0)), std::true_type{}). The advantage is simply that it's shorter.
Next, the struct has_stream : decltype(...) inherits from either std::true_type or std::false_type in the end, depending on whether the decltype check in test_stream fails or not.
Last, std::declval gives you a "value" of whatever type you pass, without you needing to know how you can construct it. Note that this is only possible inside an unevaluated context, such as decltype, sizeof and others.


Note that decltype is not necessarily needed, as sizeof (and all unevaluated contexts) got that enhancement. It's just that decltype already delivers a type and as such is just cleaner. Here's a sizeof version of one of the overloads:

template<class T> void serialize_imp(std::ostream& os, T const& obj, int,     int(*)[sizeof((os << obj),0)] = 0) {   os << obj; } 

The int and long parameters are still there for the same reason. The array pointer is used to provide a context where sizeof can be used.

like image 38
Xeo Avatar answered Sep 19 '22 05:09

Xeo