Reputation: 64945
Can I write a template function taking an argument T that calls a member function foo
if it exists on T
, and if it doesn't calls a free function foo(T)
instead (and fails to compile if neither exists)?
Something like:
template<typename T>
int call_foo(T t) {
// if T::foo() exists
return t.foo();
// else return foo(t);
}
How about the reverse case: preferring a free function foo
before the member function? I cannot use any features introduced after C++11.
Upvotes: 13
Views: 3831
Reputation: 66210
Pre C++17 you can's compile/not compile different parts of the same function with if constexpr
.
So, pre C++17, you have to do, somewhere, two different functions.
An example: if you prepare a couple of helper functions
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); }
that are SFINAE enabled only if T::foo()
exist (the first one) or if a free foo()
exist (the second one), you can write call_foo()
as follows
template <typename T>
int call_foo (T const & t)
{ return call_foo_h(t, 0); }
//......................^ a int value
Observe the second (unused) parameter in call_foo_h()
; an int
in the T::foo()
version, a long
in the free version.
Here is the trick: calling call_foo_h
with an int
(0
) you call preferably the int
version (the T::foo()
), when available, and the long
version otherwise.
How about the reverse case: preferring a free function
foo
before the member function?
In this case write call_foo()
as follows
template <typename T>
int call_foo (T const & t)
{ return call_foo_h(t, 0L); }
//......................^^ a long value
That is: call call_foo_h
with a long
value, giving the precedence to the free foo()
version.
Upvotes: 9
Reputation: 25317
This isn't too hard. There are many methods of checking whether an arbitrary expression is valid. You can combine this with if constexpr
in C++17 or tag dispatch earlier to get the behaviour you desire.
This uses C++17, but everything can be done in prior versions:
#include <type_traits>
#include <utility>
// This is just one way to write a type trait, it's 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's 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);
}
}
Upvotes: 21