Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine whether a class has a particular templated member function?

Tags:

c++

sfinae

I was wondering if it's possible to extend the SFINAE approach to detecting whether a class has a certain member function (as discussed here:

"Is there a Technique in C++ to know if a class has a member function of a given signature?" Check if a class has a member function of a given signature

) to support templated member functions? E.g. to be able to detect the function foo in the following class:

struct some_class {
   template < int _n > void foo() { }
};

I thought it might be possible to do this for a particular instantiation of foo, (e.g. check to see if void foo< 5 >() is a member) as follows:

template < typename _class, int _n >
class foo_int_checker {

  template < typename _t, void (_t::*)() >
  struct sfinae { };

  template < typename _t >
  static big
  test( sfinae< _t, &_t::foo< _n > > * );

  template < typename _t >
  static small
  test( ... );

public:

  enum { value = sizeof( test< _class >( 0 ) ) == sizeof( big ) };

};

Then do foo_int_checker< some_class, 5 >::value to check whether some_class has the member void foo< 5 >(). However on MSVC++ 2008 this always returns false while g++ gives the following syntax errors at the line test( sfinae< _t, &_t::foo< _n > > );

test.cpp:24: error: missing `>' to terminate the template argument list
test.cpp:24: error: template argument 2 is invalid
test.cpp:24: error: expected unqualified-id before '<' token
test.cpp:24: error: expected `,' or `...' before '<' token
test.cpp:24: error: ISO C++ forbids declaration of `parameter' with no type

Both seem to fail because I'm trying to get the address of a template function instantiation from a type that is itself a template parameter. Does anyone know whether this is possible or if it's disallowed by the standard for some reason?

EDIT: It seems that I missed out the ::template syntax to get g++ to compile the above code correctly. If I change the bit where I get the address of the function to &_t::template foo< _n > then the program compiles, but I get the same behaviour as MSVC++ (value is always set to false).

If I comment out the ... overload of test to force the compiler to pick the other one, I get the following compiler error in g++:

test.cpp: In instantiation of `foo_int_checker<A, 5>':
test.cpp:40:   instantiated from here
test.cpp:32: error: invalid use of undefined type `class foo_int_checker<A, 5>'
test.cpp:17: error: declaration of `class foo_int_checker<A, 5>'
test.cpp:32: error: enumerator value for `value' not integer constant

where line 32 is the enum { value = sizeof( test< _class >( 0 ) ) == sizeof( big ) }; line. Unfortunately this doesn't seem to help me diagnose the problem :(. MSVC++ gives a similar nondescript error:

error C2770: invalid explicit template argument(s) for 'clarity::meta::big checker<_checked_type>::test(checker<_checked_type>::sfinae<_t,&_t::template foo<5>> *)'

on the same line.

What's strange is that if I get the address from a specific class and not a template parameter (i.e. rather than &_t::template foo< _n > I do &some_class::template foo< _n >) then I get the correct result, but then my checker class is limited to checking a single class (some_class) for the function. Also, if I do the following:

template < typename _t, void (_t::*_f)() >
void
f0() { }

template < typename _t >
void
f1() {
  f0< _t, &_t::template foo< 5 > >();
}

and call f1< some_class >() then I DON'T get a compile error on &_t::template foo< 5 >. This suggests that the problem only arises when getting the address of a templated member function from a type that is itself a template parameter while in a SFINAE context. Argh!

like image 804
Aozine Avatar asked Nov 14 '22 13:11

Aozine


1 Answers

There is something similar already implemented in Boost.MPL, it is called "BOOST_MPL_HAS_XXX_TRAIT_DEF". See:

http://www.boost.org/doc/libs/1_41_0/libs/mpl/doc/refmanual/has-xxx-trait-def.html

It can detect if the class have a given named type.

Also, for your specific case, instead of passing the function pointer as a parameter (void (_t::*)()), try to use it in the body of your method, i.e., something like:

template < typename _t >
static big test( sfinae<_t> )
{
  &_t::foo<_n>;
}
like image 144
e.tadeu Avatar answered Dec 15 '22 00:12

e.tadeu