Why is sfinae on if constexpr not allowed?
You can also reduce the amount of code by using std::experimental::is_detected
.
In your example, the code would then look like:
template <class T>
using has_foo_t = decltype(std::declval<T>().foo());
if constexpr(is_detected_v<has_foo_t,decltype(var)>)
var.foo();
Your use of pointer to member function is a bad idea; if foo
is overloaded, it spuriously fails (you have a foo, but not just one). Who really wants "do you have exactly one foo"? Almost nobody.
Here is a briefer version:
template<class T>
using dot_foo_r = decltype( std::declval<T>().foo() );
template<class T>
using can_foo = can_apply<dot_foo_r, T>;
where
namespace details {
template<template<class...>class, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
Now, writing dot_foo_r
is a bit annoying.
With constexpr
lambdas we can make it less annoying and do it inline.
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
It does need the RETURNS
macro, at least until @Barry's submission to [](auto&&f)RETURNS(f())
be equivalent to [](auto&&f)=>f()
.
We then write can_invoke
, which is a constexpr
variant of std::is_invocable
:
template<class F>
constexpr auto can_invoke( F&& f ) {
return [](auto&&...args)->std::is_invocable<F(decltype(args)...)>{
return {};
};
}
This gives us:
if constexpr(
can_invoke([](auto&&var) RETURNS(var.foo()))(var)
) {
var.foo();
}
or using @Barry's proposed C++20 syntax:
if constexpr(can_invoke(var=>var.foo())(var)) {
var.foo();
}
and we are done.
The trick is that RETURNS
macro (or =>
C++20 feature) lets us do SFINAE on an expression. The lambda becomes an easy way to carry that expression around as a value.
You could write
[](auto&&var) ->decltype(var.foo()) { return var.foo(); }
but I think RETURNS
is worth it (and I don't like macros).
Since c++17 there is always a constexpr lambda workaround if you really need to do sfinae inline:
#include <utility>
template <class Lambda, class... Ts>
constexpr auto test_sfinae(Lambda lambda, Ts&&...)
-> decltype(lambda(std::declval<Ts>()...), bool{}) { return true; }
constexpr bool test_sfinae(...) { return false; }
template <class T>
constexpr bool bar(T var) {
if constexpr(test_sfinae([](auto v) -> decltype(v.foo()){}, var))
return true;
return false;
}
struct A {
void foo() {}
};
struct B { };
int main() {
static_assert(bar(A{}));
static_assert(!bar(B{}));
}
[live demo]