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]

Tags:

C++

C++17