c++c++17sfinae

Check for free function existence in compile time


I am writing a function wrapper for our internal API that recently changed and I would like my function to support both versions, depending on which header was included, i.e. which function version is available:

#include <iostream>
#include <string>
#include <type_traits>

// /libv1/include/api.h
void apiFoo(){
    std::cout << "withoutArg\n";
}
// end of libv1

// /libv2/include/api.h
#ifdef NEW_API
void apiFoo(std::string arg) {
    std::cout << "withArg: " << arg << "\n";
}
#endif
// end of libv2

constexpr auto g_name = "name";

void wrapper() {
    #ifdef NEW_API
        apiFoo(g_name);
    #else
        apiFoo();
    #endif
}

int main() {
    wrapper();
}

This approach with preprocessor directives works, but I would like to do it using SFINAE and/or constexpr if, if it's possible. I tried to paraphrase the classic approach struct has_method_foo that works great for class methods, but apparently I can't do it this way, because free functions are not dependent on the template parameters:

template <typename = void>
struct takesArg : std::false_type {};

template <> struct takesArg<std::void_t<decltype(apiFoo(g_name))> >
: std::true_type {};

inline constexpr bool g_newApi = takesArg<>::value;

or:

template <typename T, typename = void>
struct takesArg : std::false_type {};

template <typename T> struct takesArg<T, std::void_t<decltype(apiFoo(g_name))> >
: std::true_type {};

inline constexpr bool g_newApi = takesArg<void>::value;

I hoped to be able to:

    if constexpr(g_newApi) {
        apiFoo(g_name);
    }
    else {
        apiFoo();
    }

but all of that code fails to compile with similar message:

error: too many arguments to function 'void apiFoo()'
   31 | template <typename T> struct takesArg<T, std::void_t<decltype(apiFoo(g_name))> >

Is there a way to check for free function existence in compile time, or do I need to stick with #ifdef? I am currently restricted to C++17, but if there is C++20 solution I would also be interested.


Solution

  • Is there a way to check for free function existence in compile time

    Yes, but way to work with SFINAE is that it should have a Substitution.

    Using C++20 as example

    template <typename...Ts>
    static constexpr bool apiFooAble = requires (Ts... args){ apiFoo(args...); };
    
    #if NEW_API
    static_assert(apiFooAble<std::string>);
    #else
    static_assert(apiFooAble<>);
    #endif
    

    I hoped to be able to:

    if constexpr(g_newApi) {
       apiFoo(g_name);
    } else {
       apiFoo();
    }
    

    From if constexpr:

    Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive:

    So you have to make both branches correct. One way is to collect the argument as tuple, and apply it:

    auto getApiFooArgs()
    {
        if constexpr (apiFooAble<const char*>) {
            return std::make_tuple(g_name);
        } else {
            return std::make_tuple();
        }
    }
    
    void wrapper() {
        std::apply([](auto&&... args){ apiFoo(args...); }, getApiFooArgs());
    }
    

    Demo

    MACRO seems more appropriate here.