Вызов функции-члена, если он существует, возврат к свободной функции и наоборот
Могу ли я написать функцию шаблона с аргументом T, который вызывает функцию-член foo
, если он существует в T
, и если он не вызывает свободную функцию foo(T)
вместо (и не может скомпилироваться, если ни один существует)?
Что-то вроде:
template<typename T>
int call_foo(T t) {
// if T::foo() exists
return t.foo();
// else return foo(t);
}
Как насчет обратного случая: предпочитая свободную функцию foo
перед функцией-членом? Я не могу использовать какие-либо функции, введенные после С++ 11.
Ответы
Ответ 1
Pre С++ 17 вы можете скомпилировать/не скомпилировать разные части одной и той же функции с помощью if constexpr
.
Итак, pre С++ 17, вы должны где-то выполнять две разные функции.
Пример: если вы подготовили пару вспомогательных функций
template <typename T>
auto call_foo_h (T t, int) -> decltype( t.foo() )
{ return t.foo(); }
template <typename T>
auto call_foo_h (T t, long) -> decltype( foo(t) )
{ return foo(t); }
которые разрешены SFINAE, только если существует T::foo()
(первый) или если существует свободный foo()
(второй), вы можете написать call_foo()
следующим образом
template <typename T>
int call_foo (T const & t)
{ return call_foo_h(t, 0); }
//......................^ a int value
Обратите внимание на второй (неиспользуемый) параметр в call_foo_h()
; a int
в версии T::foo()
, a long
в бесплатной версии.
Вот трюк: вызывая call_foo_h
с int
(0
), вы предпочитаете вариант int
(T::foo()
), если он доступен, и версию long
в противном случае.
Как насчет обратного случая: предпочитаете свободную функцию foo
перед функцией-членом?
В этом случае напишите call_foo()
следующим образом
template <typename T>
int call_foo (T const & t)
{ return call_foo_h(t, 0L); }
//......................^^ a long value
То есть: call call_foo_h
со значением long
, предоставляющим приоритет свободной версии foo()
.
Ответ 2
Это не слишком сложно. Существует множество методов проверки правильности произвольного выражения. Вы можете объединить это с if constexpr
в С++ 17 или отправку тегов раньше, чтобы получить желаемое поведение.
Это использует С++ 17, но все можно сделать в предыдущих версиях:
#include <type_traits>
#include <utility>
// This is just one way to write a type trait, it not necessarily
// the best way. You could use the Detection Idiom, for example
// (http://en.cppreference.com/w/cpp/experimental/is_detected).
template <typename T, typename = void>
struct has_member_fn
: std::false_type
{};
// std::void_t is a C++17 library feature. It can be replaced
// with your own implementation of void_t, or often by making the
// decltype expression void, whether by casting or by comma operator
// (`decltype(expr, void())`)
template <typename T>
struct has_member_fn<T,
std::void_t<decltype(std::declval<T>().foo())>>
: std::true_type
{};
template <typename T, typename = void>
struct has_free_fn
: std::false_type
{};
template <typename T>
struct has_free_fn<T,
// Be wary of ADL. You're basically asking the compiler,
// "What the result of foo(T{}) if I were to call that
// here?" That syntax can call functions via ADL
std::void_t<decltype(foo(std::declval<T>()))>>
: std::true_type
{};
template <typename T>
int call_foo(T t) {
// if constexpr is C++17, but you can use tag dispatch to
// do the same in prior versions
if constexpr (has_member_fn<T>::value) {
return t.foo();
} else {
// you could make this an `else if constexpr (has_free_fn<T>::value)`
// and provide a better case for if neither exists
return foo(t);
}
}
Live on Godbolt