Страницы

Поиск по вопросам

пятница, 29 ноября 2019 г.

Как проверить есть ли у класса Т метод foo?

#c++


Есть ли возможность в C++ написать что-то похожее на это:

template
void f(T a)  {
  if ( Существет метод a.foo ) {
    a.foo();
  } else {
    myfoo(a);
  }
}


Т.е. функция должна вести себя по разному в зависимости от того есть у класса T метод
foo или нет.
    


Ответы

Ответ 1



Это делается так: #include struct A{ void foo(){} }; struct B{}; template struct Test{ typedef void(T::*P)(void); template struct True{char dummy[2];}; typedef char False; static False detect(...); template static True detect(U*); static const bool exists = (sizeof(False) != sizeof(detect(static_cast(0)))); }; int main(){ std::cout << std::boolalpha << Test::exists << std::endl; //false std::cout << std::boolalpha << Test::exists << std::endl; //true } Теперь о том, что же здесь творится. В коде используется идиома под названием SFINAE. Аббревиатура SFINAE расшифровывается как substitution failure is not an error и означает следующее: при определении перегрузок функции ошибочные инстанциации шаблонов не вызывают ошибку компиляции, а отбрасываются из списка кандидатов на наиболее подходящую перегрузку. Теперь посмотрим как будет себя вести этот код. В классе Test определено два типа True и False. Их важным свойством является то, что sizeof(False) != sizeof(True). Так же в нашем распоряжении есть два метода detect. Первый принимает указатель на T, второй принимает произвольное количество аргументов(эллипсис). Эллипсис является всеядным, и в тоже время обладает самым низким приоритетом при выборе перегрузки. При вызове detect(static_cast(0) компилятор пробует подобрать подходящую перегрузку. Более высоким приоритетом обладает шаблонная версия detect. Но если у типа T не окажется метода void foo(), то инстанцирование шаблона провалится. Но как мы помним substitution failure is not an error. Компилятор отдаст предпочтение эллипсису(тореточие). Узнать какая версия detect была выбрана мы можем по типу возвращаемого значения. А отличить типы мы можем по их размеру. Обратите внимание, в коде нет реализации методов detect. Тут используется тот факт что sizeof не вычисляет значение выражения, сразу извлекает тип. Таким образом методы detect никогда не будут вызваны. В итоге в переменной exists окажется true если у типа T есть метод void foo(). Весь код выполняется на этапе компиляции, и не дает никаких накладных расходов. Так же этот код скомпилировался онлайн-компилятором в режиме совместимости с C++98 UPD: В комментариях мне намекнули, что это не решает вашей проблемы. В какой-то мере это так. Полученное значение нельзя просто использовать в if-е, так как это приведет к ошибке компиляции. Нам нужен аналог if, который бы отрабатывал на этапе компиляции. В роли такого if-а может выступить специализация шаблонов: template::exists> struct Foo; template struct Foo{ static void foo(const T &t){ t.foo(); } }; template struct Foo{ static void foo(const T &t){ std::cout << "foo" << std::endl; } }; Структура Foo имеет две специализации. Первая для случая Test::exists == true, вторая для false. Ну и небольшая вишенка на торте для удобства, не добавляющая ничего нового кроме автоматического вывода типа аргументов: template void foo(const T &t){ Foo::foo(t); } Теперь main примет такой вид: int main(){ A a; B b; foo(a); //A::foo foo(b); //foo } Полный пример

Ответ 2



Всё ниженаписанное будет работать для C++, версии 14-го года минимум. У Вас, по сути, две проблемы: первая, это определить есть ли у класса функция, и вторая вызвать функцию в зависимости от того, есть ли она. Первая проблема решается довольно просто: template struct HasFoo: std::false_type {}; template struct HasFoo().foo()), void>::value>>: std::true_type {}; template constexpr bool HasFoo_v = HasFoo::value; Используется это так: constexpr bool hasFoo = HasFoo_v; Давайте допустим, что у нас следующие входные данные: struct A { void foo() { std::cout << "From class foo!\n"; } }; struct B {}; void foo(B) { std::cout << "From standalone foo!\n"; } Теперь, нам надо как-то вызывать разные функции, в зависимости от наличия функции foo. Тут перед нами встаёт первая проблема: если бы на дворе был 17 год, то вполне вероятно, что мы могли бы написать как-то так: A a; if constexpr (HasFoo_v
) { a.foo(); } else { foo(a); } Но у нас всё ещё 2016, поэтому придётся пойти другой дорогой: напишем свой constexpr if(жалкое подобие, хотя бы). Вот к такой реализации я пришёл: template struct ConditioanlCall { ConditioanlCall(F function) {} }; template struct ConditioanlCall { ConditioanlCall(F function) { auto ignore = false; function(ignore); } }; template void callConditional(F&& function) { ConditioanlCall{std::forward(function)}; } #define CONSTEXPR_IF(condition, action) \ callConditional([&](auto) action); Используется это так: int main() { A a; B b; CONSTEXPR_IF(!HasFoo_v, {foo(b);}); CONSTEXPR_IF(HasFoo_v, {a.foo();}); return 0; } Полный код можно найти по ссылке.

Комментариев нет:

Отправить комментарий