Borph
Borph

Reputation: 933

Can I use SFINAE to detect template class member functions?

I used SFINAE many times successfully. To detect if a class provides a function is not a problem. My current problem seems to be the opposite of his problem! Instead of also detect the derived methods, I would prefer to detect only the class' methods. It seems to be related to the fact that the method is a template.

Is it possible to detect a class template method? I tried to instantiate the template with a type which shouldn't harm, but no luck.

struct A { template<class T> void Func( T ) {}; };
struct B :A {};

template< class T >
struct CheckForFunc
{
    typedef char(&YesType)[1];
    typedef char(&NoType)[2];
    template< class U, void (U::*)( int ) > struct Sfinae;

    template< class T2 > static YesType Test( Sfinae<T2,&T2::Func>* );
    template< class T2 > static NoType  Test( ... );
    static const bool value = sizeof(Test<T>(0))==sizeof(YesType);
};

int main(int argc, char* argv[])
{
    // gives "1"
    std::cout << "Value A=" << CheckForFunc< A >::value << std::endl;
    // doesn't compile!
    std::cout << "Value B=" << CheckForFunc< B >::value << std::endl;
    return 0;
}

Error message:

error: ‘&A::Func’ is not a valid template argument for type ‘void (B::*)(int)’ because it is of type ‘void (A::*)(int)’

Note that this SFINAE works very well with template methods, just not with derivation! Bad is that it doesn't just detect wrong, it fails compilation.

How to write a SFINAE test without using a 'sample' type (here: the int)?

Edit: Sorry, C++03 only! And LLVM was fine with it, also VS2008, just not GCC and QNX (version I would have to look tomorrow).

Edit2: Didn't know about Coliru! Very cool, here is the error!

Upvotes: 2

Views: 1155

Answers (4)

Bernhard Leichtle
Bernhard Leichtle

Reputation: 181

updated to C++20 (using concepts) gives us this neat constexpr solution

#include <iostream>

struct A { template<class T> void Func( T ) {}; };
struct B : A {};
struct C {};

template <typename T>
concept CheckForFunc = requires(T t) { t.Func(T());  };

int main(int argc, char* argv[])
{
    if constexpr(CheckForFunc<A>)        std::cout << "Value A" << std::endl;
    if constexpr(CheckForFunc<B>)        std::cout << "Value B" << std::endl;
    if constexpr(CheckForFunc<C>)        std::cout << "Value C" << std::endl;
    return 0;
}

the output would be

Value A
Value B

Upvotes: 1

dyp
dyp

Reputation: 39131

If you know the exact template parameters the member function template should have, this works: (Note it uses some C++11 features that could be replaced by C++03 features)

#include <iostream>
#include <type_traits>

struct A { template<class T> void Func( T ) {} };
struct B :A {};

template< class T >
struct CheckForFunc
{
    typedef char(&YesType)[1];
    typedef char(&NoType)[2];

    template< class T2 > static YesType Test(
        typename std::enable_if<
            std::is_same<void (T::*)(int), decltype(&T2::template Func<int>)>{},
            int
        >::type );
    template< class T2 > static NoType  Test( ... );
    static const bool value = sizeof(Test<T>(0))==sizeof(YesType);
};

int main(int argc, char* argv[])
{
    // gives "1"
    std::cout << "Value A=" << CheckForFunc< A >::value << std::endl;
    // doesn't compile!
    std::cout << "Value B=" << CheckForFunc< B >::value << std::endl;
    return 0;
}

Output:

Value A=1
Value B=0

Upvotes: 0

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361612

I would use this fix:

Sfinae<T2, decltype(std::declval<T2>().Func(0))> 

That is, use the type of the expression obj.Func(0) and pass it to Sfinae class template.

Here is the complete code with the fix:

#include <iostream>
#include <utility>

struct A { template<class T> void Func( T ) {}; };

struct B : A {};

struct C {};

template< class T >
struct CheckForFunc
{
    typedef char(&YesType)[1];
    typedef char(&NoType)[2];
    template< class, class > struct Sfinae;

    template< class T2 > static YesType Test( Sfinae<T2, decltype(std::declval<T2>().Func(0))> * );
    template< class T2 > static NoType  Test( ... );
    static const bool value = sizeof(Test<T>(0))==sizeof(YesType);
};

int main(int argc, char* argv[])
{
    std::cout << "Value A=" << CheckForFunc< A >::value << std::endl;
    std::cout << "Value B=" << CheckForFunc< B >::value << std::endl;
    std::cout << "Value C=" << CheckForFunc< C >::value << std::endl;
    return 0;
}

Output:

Value A=1
Value B=1
Value C=0

I added class C to this demo.

Online Demo. :-)

Upvotes: 2

The issue is not related to a class template, which is resolved correctly, but to a weird quirk in the address-of-member expression. In particular, with the types:

struct base { void foo(); };
struct derived : base {};

The expression &derived::foo is of type void (base::*)() which might or not be intuitive.

As of a test to detect the presence of a member function template, I don't have an answer. You cannot take the address of a template, but you could probably create a fake inaccessible type and try to call the function with that type. The only way that the class could have a function taking that type would be if the function itself is a template. You might want to use this inside of an unevaluated expression to avoid odr-using the template function with your type.

Upvotes: 3

Related Questions