I want to write a function that dispatches a lambda either to an external library function template (if this function template exists within the library) or to execute the lambda directly. We can do this using SFINAE shown below:
#include <iostream>
#include <type_traits>
#ifdef _HAS_THE_FUNC
//This part comes from an external library that either provides the function (in an older version) or not (in a newer version).
namespace ext { namespace lib {
template<int N = 1, typename F>
void lib_fun(F f) {
std::cout << "LibFun<" << N << ">" << std::endl;
f();
}
} }
#endif
// "Inject" a little helper into the library's namespace to detect the presence of the function
namespace ext { namespace lib {
// Helper type to detect the presence of lib_fun
template <typename F>
struct has_lib_fun {
template <typename Fun>
static auto test(int) -> decltype(lib_fun<int{}>(std::declval<Fun>()), std::true_type{});
template <typename>
static auto test(...) -> std::false_type;
using type = decltype(test<F>(0));
static constexpr bool value = type::value;
};
// Use SFINAE to conditionally select the return type of loop_fuse
template <int N = 1, typename F>
auto safe_lib_fun(F f, int) -> std::enable_if_t<has_lib_fun<F>::value> {
lib_fun<N>(f);
}
template <int N = 1, typename F>
auto safe_lib_fun(F f, double) {
std::cout << "Workaround" << std::endl;
return f();
}
} }
int main() {
ext::lib::safe_lib_fun([]() { std::cout << "Hello World" << std::endl; }, 0);
ext::lib::safe_lib_fun<2>([]() { std::cout << "Hello World" << std::endl; }, 0);
return 0;
}
If I build and run the code, I get the expected output:
clang++ -std=c++17 main.cpp -o woFun && clang++ -std=c++17 main.cpp -D_HAS_THE_FUNC -o wFun
#./woFun
Workaround
Hello World
#./wFun
LibFun<1>
Hello World
However, when I try to build the code with GCC using c++17 (everything is fine when using c++20), I get the following errors (if the function template is not present):
<source>:20:39: error: 'lib_fun' was not declared in this scope; did you mean 'has_lib_fun'?
20 | static auto test(int) -> decltype(lib_fun<int{}>(std::declval<F>()), std::true_type{});
| ^~~~~~~
| has_lib_fun
<source>:20:39: error: 'lib_fun' was not declared in this scope; did you mean 'has_lib_fun'?
20 | static auto test(int) -> decltype(lib_fun<int{}>(std::declval<F>()), std::true_type{});
| ^~~~~~~
| has_lib_fun
<source>: In function 'std::enable_if_t<ext::lib::has_lib_fun<_F>::value> ext::lib::safe_lib_fun(_F, int)':
<source>:31:5: error: 'lib_fun' was not declared in this scope; did you mean 'has_lib_fun'?
31 | lib_fun<_N>(f);
| ^~~~~~~
How can I check for the existence of the function template prior with C++11/17?
What you're trying to do is impossible without some changes to lib_fun
.
template<int N = 1, typename F> void lib_fun(F f) { std::cout << "LibFun<" << N << ">" << std::endl; f(); }
The problem here is that N
is not deducible from the function parameters, so you would have to call it like lib_fun<N>(f)
in your expression tester (unless you never provided N
, but that defeats the purpose of this template parameter).
This runs into the problem:
Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:
- [...]
- a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or
The syntax lib_fun<N>(f)
in your expression tester has_lib_fun
is always ill-formed if lib_fun
isn't defined at that point.
The reason why lib_fun(f)
would work is that hypothetically, lib_fun
could be found through argument dependent lookup (ADL) for some arguments f
, so the compiler cannot reject it with an error despite lib_fun
not being defined.
#ifdef _HAS_THE_FUNC
namespace ext { namespace lib {
template<int _N = 1, typename _F>
void lib_fun(_F f) {
std::cout << "LibFun<" << _N << ">" << std::endl;
f();
}
} }
#else
namespace ext { namespace lib {
template<int _N = 1, typename _F>
void lib_fun(_F f) = delete;
} }
#endif
See live solution at Compiler Explorer.
If lib_fun
is deleted, not undefined when _HAS_THE_FUNC
is undefined, then the expression lib_fun<N>(f)
could still be valid, hypothetically.
It wouldn't be valid once instantiated because lib_fun
is defined as deleted, but it hypothetically could be valid for some instantiations of has_lib_fun
and specializations of lib_fun
, so the compiler can't just reject it with an error.
You've mentioned in the comments that in practice, there is no _HAS_THE_FUNC
, so you cannot define a replacement.
However, if you can't define a replacement, you can simply define an overload:
struct incomplete;
template<int _N = 1, typename _F> // we can't just write enable_if_t<false>
std::enable_if_t<std::is_same_v<_F, incomplete>> lib_fun(_F f);
See live solution at Compiler Explorer.
If incomplete
was defined at some point, then hypothetically, _F
could be the same as incomplete
.
This is why the compiler can't just reject this.
However, we never define incomplete
in practice, and thus lib_fun
is a function for which SFINAE always fails.
All names starting with an underscore followed by a capital letter are reserved for the implementation. You should avoid names like _HAS_THE_FUNC
.
As a side note, namespace ext { namespace lib
can be simplified to namespace ext::lib
since C++17.