13

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.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 2
    You can [check if a member exists](https://stackoverflow.com/a/13787502/3484570) (maybe not the best link). Not so sure about free functions. – nwp Nov 22 '17 at 21:15
  • 1
    @Justin - both answers were good, but I didn't accept one immediately since I hadn't tried them out, and then forgot! I accepted the one now that had a C++11 solution, since that's what I asked for and the other one relied on newer features. Thanks for the poke! – BeeOnRope Feb 23 '18 at 01:53

2 Answers2

20

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

Live on Godbolt

Justin
  • 24,288
  • 12
  • 92
  • 142
  • This solution generally works for me, but I have the logic reversed such that the free-function lookup is performed first. However, if the free function isn't defined BEFORE the has_free method, has_free fails, even if the free function is defined prior to the invocation of call_foo. This is MSDEV2019. Any insight? I will admit this behaviour is different between call_foo and call_foo. I wonder if built-ins are causing me trouble. – Steven Oct 08 '20 at 19:18
  • @Steven I don't know what MSDEV2019 is, but if you can use C++20, it's easiest to use concepts. Something like `requires(T t) { foo(t); }`. Otherwise, I'd want to concrete example to play around with to get the behavior you desire. If you want to ask another question on this variation, perhaps that would be appropriate. I wouldn't mind if you pinged me with a link to said question, but you'll probably get good answers regardless. – Justin Oct 08 '20 at 20:19
8

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.

max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    I accepted this answer, but [the other one](https://stackoverflow.com/a/47444032/149138) deserves a look too if you are on C++17 or above. – BeeOnRope Feb 24 '18 at 00:36
  • @BeeOnRope - I'm agree: I've up-voted the other one. – max66 Feb 24 '18 at 01:21