4

我想在编译时启用/禁用分支,具体取决于是否可以使用某些参数调用函数。有什么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;
  }
}
4

2 回答 2

4

首先,您需要使您的 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 );

现在 add 和 subract 可以在 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;
  }
}

这是;在中它更尴尬,在中更优雅,因为它们已经有一个可以调用类型特征(它处理更多的极端情况;但是,它也希望你调用addwith std::invoke)。


中,我有点喜欢这个技巧:

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

whereinvoke_test接受一个可调用对象,并返回一个可调用对象,其唯一工作是回答“使用您传递给我的参数调用原始可调用对象”。

于 2018-02-27T18:35:23.980 回答
3

您可以将 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 << "OK\n";
    }

    if constexpr ( !can_be_called(foo, A{}, 2) ) {
        std::cout << "NO\n";
    }
}
于 2018-02-27T18:29:12.563 回答