使用 "if constexpr" 和 SFINAE 禁用分支
disable branch with "if constexpr" and SFINAE
我想 enable/disable 在编译时分支,这取决于是否可以使用某些参数调用函数。
在 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;
}
}
你可以把SFINAE放到return类型,让函数重载判断是否可以调用。辅助函数 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";
}
}
首先你需要制作你的 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 );
现在可以在 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; in c++11 it is more awkward, in c++17 更优雅,因为他们已经有一个 can invoke 类型特征(处理更多的极端情况;但是,它还希望您用 [=15= 调用 add
]).
在 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
接受一个可调用对象,而 returns 一个唯一工作是回答 "the original callable be invoked with the args you passed me".
的可调用对象
我想 enable/disable 在编译时分支,这取决于是否可以使用某些参数调用函数。
在 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;
}
}
你可以把SFINAE放到return类型,让函数重载判断是否可以调用。辅助函数 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";
}
}
首先你需要制作你的 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 );
现在可以在 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; in c++11 it is more awkward, in c++17 更优雅,因为他们已经有一个 can invoke 类型特征(处理更多的极端情况;但是,它还希望您用 [=15= 调用 add
]).
在 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
接受一个可调用对象,而 returns 一个唯一工作是回答 "the original callable be invoked with the args you passed me".