根据表达式的有效性选择要应用的函数
Choose function to apply based on the validity of an expression
问题如下,在C++14
:
- 让我们有两个函数
FV&& valid_f
、FI&& invalid_f
和参数 Args&&... args
- 如果表达式
std::forward<FV>(valid_f)(std::forward<Args>(args)...)
有效,函数 apply_on_validity
应该在 args
上应用 valid_f
- 否则,如果
std::forward<FV>(invalid_f)(std::forward<Args>(args)...)
是一个有效的表达式,apply_on_validity
应该在 args
上应用 invalid_f
- 否则
apply_on_validity
什么都不做
我想代码应该是这样的:
template <class FV, class FI, class... Args, /* Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
// Apply valid_f by default
std::forward<FV>(valid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args, /* Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
// Apply invalid_f if valid_f does not work
std::forward<FV>(invalid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args, /* Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
// Do nothing when neither valid_f nor invalid_f work
}
但我真的不知道该怎么做。有什么想法吗?
Link 到泛化 .
取:
template <int N> struct rank : rank<N-1> {};
template <> struct rank<0> {};
然后:
template <class FV, class FI, class... Args>
auto apply_on_validity_impl(rank<2>, FV&& valid_f, FI&& invalid_f, Args&&... args)
-> decltype(std::forward<FV>(valid_f)(std::forward<Args>(args)...), void())
{
std::forward<FV>(valid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args>
auto apply_on_validity_impl(rank<1>, FV&& valid_f, FI&& invalid_f, Args&&... args)
-> decltype(std::forward<FI>(invalid_f)(std::forward<Args>(args)...), void())
{
std::forward<FI>(invalid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args>
void apply_on_validity_impl(rank<0>, FV&& valid_f, FI&& invalid_f, Args&&... args)
{
}
template <class FV, class FI, class... Args>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
return apply_on_validity_impl(rank<2>{}, std::forward<FV>(valid_f), std::forward<FI>(invalid_f), std::forward<Args>(args)...);
}
is superb, but code like that makes me feel compelled to point out how much cleaner C++17 will be thanks to constexpr if
and additional type traits like is_callable
: Demo Demo*此版本创建更多警告但更简单
template <class FV, class FI, class... Args>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
if constexpr (std::is_callable_v<FV(Args...)>)
std::cout << "Apply valid_f by default\n";
else
{
if constexpr (std::is_callable_v<FI(Args...)>)
std::cout << "Apply invalid_f if valid_f does not work\n";
else
std::cout << "Do nothing when neither valid_f nor invalid_f work\n";
}
}
这里有一个替代答案,只是为了好玩。我们需要 static_if
:
template <class T, class F> T&& static_if(std::true_type, T&& t, F&& ) { return std::forward<T>(t); }
template <class T, class F> F&& static_if(std::false_type, T&& , F&& f) { return std::forward<F>(f); }
还有一个is_callable
。由于您只是支持功能,我们可以这样做:
template <class Sig, class = void>
struct is_callable : std::false_type { };
template <class F, class... Args>
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>>
: std::true_type
{ };
然后我们就可以构建逻辑了:
template <class FV, class FI, class... Args>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
auto noop = [](auto&&...) {};
static_if(
is_callable<FV&&(Args&&...)>{},
std::forward<FV>(valid_f),
static_if(
std::is_callable<FI&&(Args&&...)>{},
std::forward<FI>(invalid_f),
noop
)
)(std::forward<Args>(args)...);
}
首先,C++2a 的自制版本 is_detected
:
#include <utility>
#include <type_traits>
#include <iostream>
#include <tuple>
namespace details {
template<class...>using void_t=void;
template<template<class...>class Z, class=void, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply<Z, void, Ts...>::type;
正好,std::result_of_t就是我们要测试的特质。
template<class Sig>
using can_call = can_apply< std::result_of_t, Sig >;
现在 can_call 是 true_type 如果您想要的表达式可以被调用。
现在我们写一些compile-time如果调度机械。
template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index_v{};
constexpr inline index_t<0> dispatch_index() { return {}; }
template<class B0, class...Bs,
std::enable_if_t<B0::value, int> =0
>
constexpr index_t<0> dispatch_index( B0, Bs... ) { return {}; }
template<class B0, class...Bs,
std::enable_if_t<!B0::value, int> =0
>
constexpr auto dispatch_index( B0, Bs... ) {
return index_v< 1 + dispatch_index( Bs{}...) >;
}
template<class...Bs>
auto dispatch( Bs... ) {
using I = decltype(dispatch_index( Bs{}... ));
return [](auto&&...args){
return std::get<I::value>( std::make_tuple(decltype(args)(args)..., [](auto&&...){}) );
};
}
dispatch( SomeBools...) returns 一个 lambda。 SomeBools 中的第一个 compile-time 为真(具有在布尔上下文中计算为真的 ::value)确定返回的 lambda 的作用。称之为调度索引。
它 returns 下一次调用的 dispatch_index 参数,如果是列表的 one-past-the-end 则为空 lambda。
template <class FV, class FI, class... Args /*, Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
dispatch(
can_call<FV(Args...)>{},
can_call<FI(Args...)>{}
)(
[&](auto&& valid_f, auto&&)->decltype(auto) {
return decltype(valid_f)(valid_f)(std::forward<Args>(args)...);
},
[&](auto&&, auto&& invalid_f)->decltype(auto) {
return decltype(invalid_f)(valid_f)(std::forward<Args>(args)...);
}
)(
valid_f, invalid_f
);
}
完成,live example。
我们可以使它通用以启用 nary 版本。首先index_over:
template<class=void, std::size_t...Is >
auto index_over( std::index_sequence<Is...> ){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_over(std::integral_constant<std::size_t, N> ={}){
return index_over(std::make_index_sequence<N>{} );
}
然后 auto_dispatch:
template<class...Fs>
auto auto_dispatch( Fs&&... fs ) {
auto indexer = index_over<sizeof...(fs)>();
auto helper = [&](auto I)->decltype(auto){
return std::get<decltype(I)::value>( std::forward_as_tuple( decltype(fs)(fs)... ) );
};
return indexer
(
[helper](auto...Is){
auto fs_tuple = std::forward_as_tuple( helper(Is)... );
return [fs_tuple](auto&&...args) {
auto dispatcher = dispatch(can_call<Fs(decltype(args)...)>{}...);
auto&& f0 = dispatcher(std::get<decltype(Is)::value>(fs_tuple)...);
std::forward<decltype(f0)>(f0)(decltype(args)(args)...);
};
}
);
}
带有测试码:
auto a = [](int x){ std::cout << x << "\n"; };
auto b = [](std::string y){ std::cout << y << "\n"; };
struct Foo {};
auto c = [](Foo){ std::cout << "Foo\n"; };
int main() {
auto_dispatch(a, c)( 7 );
auto_dispatch(a, c)( Foo{} );
auto_dispatch(a, b, c)( Foo{} );
auto_dispatch(a, b, c)( "hello world" );
}
问题如下,在C++14
:
- 让我们有两个函数
FV&& valid_f
、FI&& invalid_f
和参数Args&&... args
- 如果表达式
std::forward<FV>(valid_f)(std::forward<Args>(args)...)
有效,函数apply_on_validity
应该在args
上应用valid_f
- 否则,如果
std::forward<FV>(invalid_f)(std::forward<Args>(args)...)
是一个有效的表达式,apply_on_validity
应该在args
上应用 - 否则
apply_on_validity
什么都不做
invalid_f
我想代码应该是这样的:
template <class FV, class FI, class... Args, /* Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
// Apply valid_f by default
std::forward<FV>(valid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args, /* Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
// Apply invalid_f if valid_f does not work
std::forward<FV>(invalid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args, /* Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
// Do nothing when neither valid_f nor invalid_f work
}
但我真的不知道该怎么做。有什么想法吗?
Link 到泛化
取:
template <int N> struct rank : rank<N-1> {};
template <> struct rank<0> {};
然后:
template <class FV, class FI, class... Args>
auto apply_on_validity_impl(rank<2>, FV&& valid_f, FI&& invalid_f, Args&&... args)
-> decltype(std::forward<FV>(valid_f)(std::forward<Args>(args)...), void())
{
std::forward<FV>(valid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args>
auto apply_on_validity_impl(rank<1>, FV&& valid_f, FI&& invalid_f, Args&&... args)
-> decltype(std::forward<FI>(invalid_f)(std::forward<Args>(args)...), void())
{
std::forward<FI>(invalid_f)(std::forward<Args>(args)...);
}
template <class FV, class FI, class... Args>
void apply_on_validity_impl(rank<0>, FV&& valid_f, FI&& invalid_f, Args&&... args)
{
}
template <class FV, class FI, class... Args>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
return apply_on_validity_impl(rank<2>{}, std::forward<FV>(valid_f), std::forward<FI>(invalid_f), std::forward<Args>(args)...);
}
constexpr if
and additional type traits like is_callable
: Demo Demo*此版本创建更多警告但更简单
template <class FV, class FI, class... Args>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
if constexpr (std::is_callable_v<FV(Args...)>)
std::cout << "Apply valid_f by default\n";
else
{
if constexpr (std::is_callable_v<FI(Args...)>)
std::cout << "Apply invalid_f if valid_f does not work\n";
else
std::cout << "Do nothing when neither valid_f nor invalid_f work\n";
}
}
这里有一个替代答案,只是为了好玩。我们需要 static_if
:
template <class T, class F> T&& static_if(std::true_type, T&& t, F&& ) { return std::forward<T>(t); }
template <class T, class F> F&& static_if(std::false_type, T&& , F&& f) { return std::forward<F>(f); }
还有一个is_callable
。由于您只是支持功能,我们可以这样做:
template <class Sig, class = void>
struct is_callable : std::false_type { };
template <class F, class... Args>
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>>
: std::true_type
{ };
然后我们就可以构建逻辑了:
template <class FV, class FI, class... Args>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
auto noop = [](auto&&...) {};
static_if(
is_callable<FV&&(Args&&...)>{},
std::forward<FV>(valid_f),
static_if(
std::is_callable<FI&&(Args&&...)>{},
std::forward<FI>(invalid_f),
noop
)
)(std::forward<Args>(args)...);
}
首先,C++2a 的自制版本 is_detected
:
#include <utility>
#include <type_traits>
#include <iostream>
#include <tuple>
namespace details {
template<class...>using void_t=void;
template<template<class...>class Z, class=void, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply<Z, void, Ts...>::type;
正好,std::result_of_t就是我们要测试的特质。
template<class Sig>
using can_call = can_apply< std::result_of_t, Sig >;
现在 can_call
现在我们写一些compile-time如果调度机械。
template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index_v{};
constexpr inline index_t<0> dispatch_index() { return {}; }
template<class B0, class...Bs,
std::enable_if_t<B0::value, int> =0
>
constexpr index_t<0> dispatch_index( B0, Bs... ) { return {}; }
template<class B0, class...Bs,
std::enable_if_t<!B0::value, int> =0
>
constexpr auto dispatch_index( B0, Bs... ) {
return index_v< 1 + dispatch_index( Bs{}...) >;
}
template<class...Bs>
auto dispatch( Bs... ) {
using I = decltype(dispatch_index( Bs{}... ));
return [](auto&&...args){
return std::get<I::value>( std::make_tuple(decltype(args)(args)..., [](auto&&...){}) );
};
}
dispatch( SomeBools...) returns 一个 lambda。 SomeBools 中的第一个 compile-time 为真(具有在布尔上下文中计算为真的 ::value)确定返回的 lambda 的作用。称之为调度索引。
它 returns 下一次调用的 dispatch_index 参数,如果是列表的 one-past-the-end 则为空 lambda。
template <class FV, class FI, class... Args /*, Some template metaprog here */>
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args)
{
dispatch(
can_call<FV(Args...)>{},
can_call<FI(Args...)>{}
)(
[&](auto&& valid_f, auto&&)->decltype(auto) {
return decltype(valid_f)(valid_f)(std::forward<Args>(args)...);
},
[&](auto&&, auto&& invalid_f)->decltype(auto) {
return decltype(invalid_f)(valid_f)(std::forward<Args>(args)...);
}
)(
valid_f, invalid_f
);
}
完成,live example。
我们可以使它通用以启用 nary 版本。首先index_over:
template<class=void, std::size_t...Is >
auto index_over( std::index_sequence<Is...> ){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_over(std::integral_constant<std::size_t, N> ={}){
return index_over(std::make_index_sequence<N>{} );
}
然后 auto_dispatch:
template<class...Fs>
auto auto_dispatch( Fs&&... fs ) {
auto indexer = index_over<sizeof...(fs)>();
auto helper = [&](auto I)->decltype(auto){
return std::get<decltype(I)::value>( std::forward_as_tuple( decltype(fs)(fs)... ) );
};
return indexer
(
[helper](auto...Is){
auto fs_tuple = std::forward_as_tuple( helper(Is)... );
return [fs_tuple](auto&&...args) {
auto dispatcher = dispatch(can_call<Fs(decltype(args)...)>{}...);
auto&& f0 = dispatcher(std::get<decltype(Is)::value>(fs_tuple)...);
std::forward<decltype(f0)>(f0)(decltype(args)(args)...);
};
}
);
}
带有测试码:
auto a = [](int x){ std::cout << x << "\n"; };
auto b = [](std::string y){ std::cout << y << "\n"; };
struct Foo {};
auto c = [](Foo){ std::cout << "Foo\n"; };
int main() {
auto_dispatch(a, c)( 7 );
auto_dispatch(a, c)( Foo{} );
auto_dispatch(a, b, c)( Foo{} );
auto_dispatch(a, b, c)( "hello world" );
}