Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

disable branch with "if constexpr" and SFINAE

I want to enable/disable branches at compile time depending of whether a function can be called with certain arguments. What has to go in the if constexpr condition? I can get the result type via std::result_of(decltype(add)(A, B)), but how can I check, whether the result type is valid? (i.e. how to I convert this information to a bool?)

const auto add = [](const auto a, const auto b) { return a + b; };

const auto subtract = [](const auto a, const auto b) { return a - b; };

template <typename A, typename B>
void foo(A a, B b) {

  if constexpr ( /* can add(a, b) be called? */ ) {
    std::cout << "result of add: " << add(a, b) << std::endl;
  }

  if constexpr ( /* can subtract(a, b) be called? */ ) {
    std::cout << "result of subtract: " << subtract(a, b) << std::endl;
  }
}
like image 276
jfrohnhofen Avatar asked Mar 07 '23 19:03

jfrohnhofen


1 Answers

First you need to make your lambdas SFINAE-friendly.

#define RETURNS(...)\
  noexcept(noexcept(__VA_ARGS__))\
  ->decltype(__VA_ARGS__)\
  { return __VA_ARGS__; }

const auto add = [](const auto a, const auto b) RETURNS( a + b );

const auto subtract = [](const auto a, const auto b) RETURNS( a - b );

Now add and subract can be tested in a SFINAE context.

namespace details {
  template<class, class, class...>
  struct can_invoke:std::false_type {};
  template<class F, class...Args>
  struct can_invoke<F, std::void_t< std::result_of_t< F&&(Args&&...) > >, Args... >:
    std::true_type
  {};
}
template<class F, class...Args>
using can_invoke_t = details::can_invoke<F, Args...>;

template<class F, class...Args>
constexpr can_invoke_t< F, Args... >
can_invoke( F&&, Args&&... ){ return {}; }

and we are ready:

template <typename A, typename B>
void foo(A a, B b) {

  if constexpr ( can_invoke( add, a, b ) ) {
    std::cout << "result of add: " << add(a, b) << std::endl;
  }

  if constexpr ( can_invoke( subtract, a, b ) {
    std::cout << "result of subtract: " << subtract(a, b) << std::endl;
  }
}

this is c++14; in c++11 it is more awkward, in c++17 more elegant as they already have a can invoke type trait (which handles a few more corner cases; however, it also expects you to call add with std::invoke).


In c++17 I sort of like this trick:

template<class F>
constexpr auto invoke_test( F&& ) {
  return [](auto&&...args) ->
  can_invoke_t<F, decltype(args)...>
  { return {}; };
}

template <typename A, typename B>
void foo(A a, B b) {

  if constexpr ( invoke_test( add )( a, b ) ) {
    std::cout << "result of add: " << add(a, b) << std::endl;
  }

  if constexpr ( invoke_test( subtract )( a, b ) {
    std::cout << "result of subtract: " << subtract(a, b) << std::endl;
  }
}

where invoke_test takes a callable, and returns a callable whose only job is to answer "the original callable be invoked with the args you passed me".

like image 191
Yakk - Adam Nevraumont Avatar answered Mar 16 '23 00:03

Yakk - Adam Nevraumont