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.
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());
}
MACRO seems more appropriate here.