views:

4181

answers:

10

Is it possible to write a C++ 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.

A: 

No, that isn't possible. SFINEA can detect for missing subtypes, but not for missing methods.

ETA: it seems Nicola found a way to do it, but I think that's not portable.

Leon Timmermans
Yeah nicola's way isn't portable. But there are other ways to do it. All have their pitfalls, though: Mine won't detect functions that are inherited from base classes, and another way @Roshan linked to has other problems (see my comments on his answer). And nicolas way doesn't work in case the function is overloaded
Johannes Schaub - litb
+4  A: 

This is what type traits are there for. Unfortunately, they have to defined manually. In your case, imagine the following:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}
Konrad Rudolph
+1: traits have always been a clean way to solve problems.
Nicola Bonelli
you should prefer enum for traits instead of static constants : " Static constant members are lvalues,which forces the compiler to instantiate and allocate the definition for the static member. As a result, the computation is no longer limited to a pure "compile-time" effect."
Comptrol
"Enumeration values aren't lvalues(that is,they don't have an address).So, when you pass them "by reference," no static memory is used. It's almost exactly as if you passed the computed value as a literal. These considerations motivate us to use enumeration values" C++ Templates:The Complete Guide
Comptrol
Comptrol: no, the cited passage doesn't apply here since integer type static constants are a special case! They behave *exactly* like an enum here and are the preferred way. The old enum hack was only necessary on compilers that didn't follow the C++ standard.
Konrad Rudolph
Thanks for correcting Rudolph.
Comptrol
@Konrad: Unfortunately, that's not the case. "The member shall still be defined in a namespace scope if it is used in the program .." [C++03 9.4.2/4]
Roger Pate
Konrad Rudolph
@Roger Pate: Furthermore, this usage (explicitly **without** a definition) is common practice in eminent C++ libraries, e.g. in several Boost libraries (Boost.MPL, to name just one).
Konrad Rudolph
"used" refers to the definition of "used" in the one definition rule. The `1 ? a : a` expression [uses `a`](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#712) for example.
Johannes Schaub - litb
@Johannes Schaub - litb: Interesting – so this means that Boost is strictly speaking not standard conforming? Will the proposed resolution be part of the next standard? Under the explanation of “review” it says that the working group has reached an informal consensus.
Konrad Rudolph
Johannes Schaub - litb
@Johannes Schaub - litb: It doesn’t, in general. `int_::value` seems to be an exception if it compiles. Others definitely [don’t provide definitions](http://www.boost.org/doc/libs/1_41_0/libs/math/test/std_real_concept_check.cpp). Some libraries seem to use a compile-time flag `BOOST_NO_INCLASS_MEMBER_INITIALIZATION` for the decision.
Konrad Rudolph
@Konrad, in-class initializations don't preclude an out-of-class definition. So that macro just seems to say that it won't or will initialize in class, but it doesn't seem to say something about an out-of-class definition being provided. The link points to a test, probably they already knew that they don't access the object properties of the constants and thus didn't provide definitions. But i certainly won't say that all of boost is conformant, there surely are nonconformant bits hidden somewhere ^^
Johannes Schaub - litb
@Johannes Schaub - litb: “in-class initializations don't preclude an out-of-class definition” I know but the macro was used in precisely the context of out-of class definitions (with initialization being inline) in some file. The name poorly reflects that, granted.
Konrad Rudolph
+1  A: 

SFINAE can detect an existing member function. A missing function can then be handled by the default case.

Luc Hermitte
I agree with Nicola. Luc: show us working code.
Leon Timmermans
Sorry for the hurried reply. With SFINAE is possible to test if a given method is available in a class
Nicola Bonelli
A: 

I don't know Boost but it kinda looks like you could try binding to a class member function and then base on error it returns...

Marcin Gil
Would be a compile-time error.
MSalters
+21  A: 

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;
    typedef long two;

    template <typename C> static one test( typeof(&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.

Nicola Bonelli
Ach you beat me to it! It's a very nice trick this one (confirmed working with GCC 4.1 on Mac OSX 10.5)
Although, I used the following for 'one' and 'two':typedef char Small;class Big{char dummy[2];}to ensure no ambiguity about platform dependent variable size.
I doubt it exists on earth a platform with the sizeof(char) == sizeof(long)
Nicola Bonelli
Well indeed, but strictly it's not guaranteed if you're being really standards correct. (P.S. I have seen platforms where this is the case, but the compilers would choke on that code anyway ;) )
I'm not entirely sure, but I don't think this is portable. typeof is a GCC extension, this will not work on other compilers.
Leon Timmermans
If you're really concerned about that size that could match, using boost::is_same may be more robust.
Leon Timmermans
Leon, do you think is possible to ride of typeof() using a different method ? I was thinking about tr1::results_of...
Nicola Bonelli
typeof isn't needed - char[sizeof(. It must have a size >=2
MSalters
I reworked this solution a bit so it doesn't require typeof. Check out my answer near the bottom of this page. Actually what I did was to merge Nicola's and Johannes's answers and try to give helpful names to all the constructs. Hope it helps.
FireAphis
+1  A: 
Michael Burr
No, it's not standards compliant, though I think it will work in GCC if you turn on the -fpermissive option.
Leon Timmermans
I know the comments don't give a lot of room, but could you point to information on why it's not standards compliant? (I'm not arguing - I'm curious)
Michael Burr
Mike B: the standard says in 3.10 p15:"If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined" and that list indeed doesn't include the case you do.
Johannes Schaub - litb
i'm not sure why it doesnt add another comment of me: your toString call is unqualified. so it will always call the free function and never the one in the base, since the baseclass is dependant on a template type parameter.
Johannes Schaub - litb
@litb: Thanks for the pointers. I don't think 3.10 applies here. The call to toString() inside of doToString() is not "accessing the stored value of an object through an lvalue". But your 2nd comment is correct. I'll update the answer.
Michael Burr
You don't know whether doToString is virtual in the base-class. If it is, then you access the v-table of the object.
Johannes Schaub - litb
+1: Great job guys!
Nicola Bonelli
hold on, i've got the explicit quote from the standard about this: 9.3.1/1: "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined." This was just luck, someone cited it, and told me where he has it from :)
Johannes Schaub - litb
+19  A: 

C++ allows SFINAE to be used for this:

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static char (& chk(type_check<Sign, &_1::func> *))[1]; \
        template <typename   > static char (& chk(...))[2];             \
        static bool const value = sizeof(chk<T>(0)) == 1;               \
    }

the above template and macro tries to instantiate a template, giving it a member function pointer type, and the actual member function pointer. If the types to not fit, SFINAE causes the template to be ignored. Usage like this:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

But note that you cannot just call that toString function in that if branch. since the compiler will check for validity in both branches, that would fail for cases the function doesnt exist. on way is to use sfinae once again (enable_if can be gotten from boost too):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Have fun using it. The advantage of it is that it also works for overloaded member functions, and also for const member functions (remember using std::string(T::*)() const as the member function pointer type then!).

Johannes Schaub - litb
what is the chk used in the macro?
lurscher
@lurscher it's an overloaded function, where the first overload returns a ref to char array of size 1, and the other of size 2. Using SFINAE the one is masked out.
Johannes Schaub - litb
+1  A: 

The standard C++ solution presented here by litb will not work as expected if the method happens to be defined in a base class.

For a solution that handles this situation refer to :

In Russian : http://www.rsdn.ru/forum/message/2759773.1.aspx

English Translation by Roman.Perepelitsa : http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

It is insanely clever. However one issue with this solutiion is that gives compiler errors if the type being tested is one that cannot be used as a base class (e.g. primitive types)

In Visual Studio, I noticed that if working with method having no arguments, an extra pair of redundant ( ) needs to be inserted around the argments to deduce( ) in the sizeof expression.

thanks for showing this to us. I really love that guys code!
Johannes Schaub - litb
Hmm, having developed my own version using that posts ideas, i found the idea has some other drawbacks so i removed the code from my answer again. One is that all functions have to be public in the target type. So you cannot check for a "f" function in this: `struct g { void f(); private: void f(int); };` because one of the functions is private (this is because the code does `using g::f;`, which makes it fail if any `f` is not accessible).
Johannes Schaub - litb
+1  A: 

Strange nobody suggested the following nice trick I saw once on this very site :

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

You have to make sure T is a class. It seems that ambiguity in the lookup of foo is a substitution failure. I made it work on gcc, not sure if it is standard though.

Alexandre C.
+4  A: 

Though this question is two years old, I'll dare to add my answer. Hopefully it will clarify the previous, indisputably excellent, solution. I took the very helpful answers of Nicola Bonelli and Johannes Schaub and merged them into a solution that is, IMHO, more readable, clear and does not require the typeof extension:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

I checked it with gcc 4.1.2. The credit goes mainly to Nicola Bonelli and Johannes Schaub, so give them a vote up if my answer helps you :)

FireAphis