Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a member function if it exists, falling back to a free function and vice-versa

Tags:

c++

c++11

sfinae

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.

like image 394
BeeOnRope Avatar asked Nov 22 '17 21:11

BeeOnRope


1 Answers

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

like image 156
Justin Avatar answered Sep 30 '22 02:09

Justin