禁用具有"if constexpr"和 SFINAE 的分支

disable branch with "if constexpr" and SFINAE

本文关键字:SFINAE 分支 constexpr if      更新时间:2023-10-16

我想根据是否可以使用某些参数调用函数在编译时间启用/禁用分支。在if constexpr条件下必须做什么?我可以通过std::result_of(decltype(add)(A, B))获得结果类型,但是如何检查结果类型是否有效?(即如何将此信息转换为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;
  }
}

首先,您需要使您的lambdas sfinae友好。

#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 );

现在可以在sfinae上下文中测试添加和亚曲线。

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 {}; }

我们准备就绪:

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;
  }
}

这是C 14;在C 11中,它更加尴尬,在C 17中更优雅,因为它们已经具有可以调用类型的特征(它处理了更多的角案例;但是,它也希望您使用std::invoke调用add(。bk_hr>

在C 17中,我有点像这样的技巧:

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;
  }
}

invoke_test占用可可的地方,然后返回一个可调用的工作,其唯一的工作是回答"与我传递的args一起调用原始可召唤"。

您可以将sfinae放入返回类型,并让函数超载告诉呼叫是否可以进行。辅助功能can_be_called可以如下实现:

#include <type_traits>
template<class Func, class... Args>
constexpr auto
can_be_called(Func&& func, Args&&... args)
    -> decltype(
            (std::forward<Func>(func)(std::forward<Args>(args)...)
            , bool{}))
{ return true; }
struct Dummy {
    template<class T> constexpr Dummy(T&&) {}
};
template<class... Args>
constexpr bool 
can_be_called(Dummy, Args&&...) { return false; }
// test
#include <iostream>
void foo(int, int) {}
struct A{};
int main() {
    if constexpr( can_be_called(foo, 1, 2) ) {
        std::cout << "OKn";
    }
    if constexpr ( !can_be_called(foo, A{}, 2) ) {
        std::cout << "NOn";
    }
}