Как определить, имеет ли класс определенную шаблонную функцию-член? - PullRequest
5 голосов
/ 24 января 2010

Мне было интересно, возможно ли расширить подход SFINAE, чтобы определить, имеет ли класс определенную функцию-член (как обсуждено здесь:

"Есть ли в C ++ метод, позволяющий узнать, имеет ли класс функцию-член с заданной сигнатурой?" Проверить, имеет ли класс функцию-член с заданной сигнатурой

) для поддержки шаблонных функций-членов? Например. чтобы можно было обнаружить функцию foo в следующем классе:

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

Я подумал, что это возможно сделать для конкретного экземпляра foo (например, проверьте, является ли void foo< 5 >() член) следующим образом:

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 ) };

};

Затем выполните foo_int_checker< some_class, 5 >::value, чтобы проверить, есть ли в some_class элемент void foo< 5 >(). Однако в MSVC ++ 2008 это всегда возвращает false, тогда как g ++ выдает следующие синтаксические ошибки в строке 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

Кажется, что оба сбоя, потому что я пытаюсь получить адрес экземпляра функции шаблона от типа, который сам является параметром шаблона. Кто-нибудь знает, возможно ли это или по каким-то причинам запрещено стандартом?

EDIT: похоже, я пропустил синтаксис ::template, чтобы g ++ правильно скомпилировал приведенный выше код. Если я изменяю бит, в котором получаю адрес функции, на &_t::template foo< _n >, то программа компилируется, но я получаю то же поведение, что и MSVC ++ (value всегда устанавливается на false).

Если я закомментирую перегрузку ... test, чтобы заставить компилятор выбрать другой, я получу следующую ошибку компилятора в 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

, где строка 32 - это строка enum { value = sizeof( test< _class >( 0 ) ) == sizeof( big ) };. К сожалению, это не помогает мне диагностировать проблему :(. MSVC ++ выдает похожую неописуемую ошибку:

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

на той же строке.

Странно то, что если я получаю адрес из определенного класса, а не параметра шаблона (т.е. вместо &_t::template foo< _n > я делаю &some_class::template foo< _n >), тогда я получаю правильный результат, но тогда мой класс проверки ограничивается проверкой один класс (some_class) для функции. Также, если я сделаю следующее:

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

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

и позвоните f1< some_class >(), тогда я не получу ошибку компиляции на &_t::template foo< 5 >. Это говорит о том, что проблема возникает только при получении адреса шаблонной функции-члена от типа, который сам является параметром шаблона в контексте SFINAE. Argh!

1 Ответ

1 голос
/ 25 января 2010

В Boost.MPL уже реализовано нечто подобное, оно называется "BOOST_MPL_HAS_XXX_TRAIT_DEF". См:

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

Он может определить, есть ли у класса заданное имя тип .

Кроме того, для вашего конкретного случая вместо передачи указателя функции в качестве параметра (void (_t :: *) ()), попробуйте использовать его в теле вашего метода, то есть что-то вроде:

template < typename _t >
static big test( sfinae<_t> )
{
  &_t::foo<_n>;
}
...