2

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!

Community
  • 1
  • 1
Borph
  • 842
  • 1
  • 6
  • 17
  • 1
    Why not just use `Test(void (T2::*)(int) = nullptr)` and `sizeof(Test())`? – Kerrek SB Aug 06 '13 at 16:56
  • 1
    [Your code compiles, using clang++](http://coliru.stacked-crooked.com/view?id=869eb009d893d56f344c9c7c09356b5d-e223fd4a885a77b520bbfe69dda8fb91). I find that strange! I think it is a bug in clang! – Nawaz Aug 06 '13 at 17:05
  • Interesting thought to use default argument. Is nullptr C++11? – Borph Aug 06 '13 at 19:00
  • The actual question is whether `Sfinae*` leads to a substitution failure or not (latter case: code does not compile) for `T2 == B`. I tend to say it *is* a substitution failure, in that case, clang++ is right and g++ is wrong. The problem behind it, as David Rodríguez pointed out, is that a conversion from `void (A::*)(int)` to `void (B::*)(int)` is not allowed in the context of a template argument. – dyp Aug 06 '13 at 19:21
  • @KerrekSB: This wouln't be a test for T2::Func(), would it? DyP: Yes, seems to be the problem, but how to test then with GCC? – Borph Aug 06 '13 at 19:41
  • @DyP: It **should** be an SFINAE error. The fact that the substitution failed (since the member is of a different type, and that type cannot be initialized with the given pointer to member to the base type) is not an error. – David Rodríguez - dribeas Aug 06 '13 at 20:47

4 Answers4

3

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    "The expression &derived::foo is of type void (base::*)()" This is correct, but I don't think it's the problem here. The OP wants to detect a member function template that is *not* inherited (as far as I understand the OP). The expression `&A::Func` has no type yet [over.over], but is matched with both `void (A::*)(int)` and `void (B::*)(int)`. – dyp Aug 06 '13 at 17:37
  • @DyP: `&A::Func` cannot be matched with `void (B::*)(int)` on a template, as it requires a conversion from pointer-to-member of base to pointer-to-member of derived which is not one of the valid conversions for a non-type template argument. You are right in that I skipped the part where `&A::Func` is a set of overload resolution candidates, but all of them are `void (A::*)(T)` for some type `T` (i.e. `A` is fixed accross the whole set. – David Rodríguez - dribeas Aug 06 '13 at 17:46
  • Yes, I thought that as well at first, but there's a comment in [over.over]: "[Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. —end note]" – dyp Aug 06 '13 at 17:48
  • 2
    @DyP: Yes, that quote supports my point. The type of the member is ignored for overload resolution, so it tries to match `void (derived::*)(int)` and `void (base::*)(int)` is selected by overload resolution *even if `base != derived`* as the type holding the member is ignored. At this point (after overload resolution) and according to 5.3.1/3 the type of the expression is determined to be `void (base::*)(int)` **not** `void (derived::*)(int)` even though the latter was used to drive overload resolution. – David Rodríguez - dribeas Aug 06 '13 at 17:55
  • Note that outside of a template this is perfectly fine, as there is an implicit conversion from `void (base::*)(int)` to `void (derived::*)(int)`, but that conversion is not one of the allowed conversions when matching a non-type template argument. – David Rodríguez - dribeas Aug 06 '13 at 17:57
2

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. :-)

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • I think the OP wants the output `Value B=0`: "Instead of also detect the derived methods, I would prefer to detect only the class' methods" – dyp Aug 06 '13 at 17:34
  • 1
    @DyP: That doesn't make sense to me (neither do I think that is possible), because base methods, once inherited, becomes class methods of the derived class. – Nawaz Aug 06 '13 at 17:37
  • 1
    See David Rodríguez' answer. For non-overloaded functions (and non-templates), the type of the expression `&B::Func` is `void (A::*)(int)`. Don't know why the OP needs that check, though. – dyp Aug 06 '13 at 17:38
  • @DyP: Correct. Nawaz: According [to this impossible](http://stackoverflow.com/a/11008557/1844547). – Borph Aug 06 '13 at 19:11
1

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
0

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

dyp
  • 38,334
  • 13
  • 112
  • 177