应用一组 N 个函数中的第一个有效函数
Apply the first valid function of a set of N functions
之前的回答显示了如何根据调用的有效性应用函数:。但是,它适用于两个函数。我想知道这个概念是否可以在 C++14 中使用智能模板编程技巧推广到 N
函数。
问题如下:
template <std::size_t N, class... X>
/* [Return type] */ apply_on_validity(X&&... x)
{
// Tricks here
}
// The function would be equivalent to a hypothetical
// template <std::size_t N, class... F, class... Args>
// [Return type] apply_on_validity(F&&... f, Args&&... args)
// where N = sizeof...(F) is the number of functions
在执行方面:
apply_on_validity<3>(f1, f2, f3, a, b, c, d, e);
会:
- 如果表达式有效则调用
f1(a, b, c, d, e)
,否则
- 如果表达式有效则调用
f2(a, b, c, d, e)
,否则
- 如果表达式有效则调用
f3(a, b, c, d, e)
,否则
- 什么都不做
在所有情况下,它都会 return 执行函数的结果。
在调用方,模板参数3
表示已经指定的函数个数
它在 C++14 中可行吗?如果可行,怎么做?
在c++14中:
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <std::size_t N, std::size_t... Js, typename Args>
auto apply_on_validity_impl(int, std::integral_constant<std::size_t, N>, std::index_sequence<Js...>, Args&& args)
{
// Nothing here
}
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
auto apply_on_validity_impl(int, std::integral_constant<std::size_t, I>, std::index_sequence<Js...>, Args&& args)
-> decltype(std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...))
{
return std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...);
}
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
decltype(auto) apply_on_validity_impl(char, std::integral_constant<std::size_t, I>, std::index_sequence<Js...> seq, Args&& args)
{
return apply_on_validity_impl<N>(0, std::integral_constant<std::size_t, I + 1>{}, seq, std::forward<Args>(args));
}
template <std::size_t N, typename... Args>
decltype(auto) apply_on_validity(Args&&... args)
{
return apply_on_validity_impl<N>(0, std::integral_constant<std::size_t, 0>{}, std::make_index_sequence<sizeof...(Args) - N>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
在c++17中:
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
decltype(auto) apply_on_validity_impl(std::index_sequence<Js...> seq, Args&& args)
{
if constexpr (I == N)
{
}
else if constexpr (std::is_invocable_v<std::tuple_element_t<I, Args>, std::tuple_element_t<Js + N, Args>...>)
{
return std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...);
}
else
{
return apply_on_validity_impl<N, I + 1>(seq, std::forward<Args>(args));
}
}
template <std::size_t N, typename... Args>
decltype(auto) apply_on_validity(Args&&... args)
{
return apply_on_validity_impl<N, 0>(std::make_index_sequence<sizeof...(Args) - N>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
您使用的语法有点笨拙,因为您必须确切地知道您有多少个函数。结合 ,这解决了这个问题:
// Would be a local class except for the fact that it needs
// to have templates, thus can't be local to the function.
template<class... Fs>
class apply_first_f final {
public:
apply_first_f(Fs&&... fs)
: fs_{ std::forward<Fs>(fs)... }
{}
template<class... Args>
decltype(auto) operator()(Args&&... args) const
{
return apply_impl<sizeof...(Args)>(std::make_index_sequence<sizeof...(Fs)>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
private:
std::tuple<std::decay_t<Fs>...> fs_;
template<size_t argsSize, size_t... Is, class Args>
decltype(auto) apply_impl(std::index_sequence<Is...>, Args&& args) const
{
return apply_on_validity_impl<sizeof...(Fs)>(
0,
std::integral_constant<size_t, 0>{},
std::make_index_sequence<argsSize>{},
std::tuple_cat(
std::forward_as_tuple(std::get<Is>(fs_)...),
std::forward<Args>(args)
)
);
}
};
template<class... Fs>
auto make_apply_first_valid_f(Fs&&... fs)
{
return apply_first_f<Fs...>{ std::forward<Fs>(fs)... };
}
函数可以这样使用:
make_apply_first_valid_f(f1, f2, f3)(args);
DEMO(改编自 Piotr 的演示)
将它与 Piotr 的 C++ 1z 示例一起使用只需要一个小改动:
template<class... Fs>
class apply_first_f final {
// ...
template<size_t argsSize, size_t... Is, class Args>
decltype(auto) apply_impl(std::index_sequence<Is...>, Args&& args) const
{
return apply_on_validity_impl<sizeof...(Fs), /* added */ 0>(
/* 0, */
/* std::integral_constant<size_t, 0>{}, */
std::make_index_sequence<argsSize>{},
std::tuple_cat(
std::forward_as_tuple(std::get<Is>(fs_)...),
std::forward<Args>(args)
)
);
}
// ...
};
我在之前的迭代中已经回答过这个问题,但是 nary 版本有一个错误,而 clang 有一个内部编译器错误,很难找到这个错误。
我已经修复了这个错误。所以这里就一堆元编程,后面解决你的问题。
首先,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 iff 你想要的表达式可以被调用。
现在我们写一些compile-timeif dispatch machinery.
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。
现在我们希望能够调度一组可能性。为了简单起见(呵呵),我们将使用 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)>();
// some compilers dislike lambdas with unexpanded parameter packs.
// this helps with that:
auto helper = [&](auto I)->decltype(auto){
return std::get<decltype(I)::value>( std::forward_as_tuple( decltype(fs)(fs)... ) );
};
// Get 0 through N-1 as compile-time constants:
return indexer
(
[helper](auto...Is){
// make tuple of functions:
auto fs_tuple = std::forward_as_tuple( helper(Is)... );
// This is what is returned from the `auto_dispatch` function
// it perfect forwards into the correct lambda passed to `auto_dispatch`
// based on which is the first one which can be invoked by
// args...
return [fs_tuple](auto&&...args) {
// dispatcher knows which one can be called
auto dispatcher = dispatch(can_call<Fs(decltype(args)...)>{}...);
// here we get the first one that can be called, or an empty lambda:
auto&& f0 = dispatcher(std::get<decltype(Is)::value>(fs_tuple)...);
// here we do the actual call:
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" );
}
上面唯一的 N-ary 递归模板实例是 dispatch_index
。我可以通过一些工作(分而治之)来实现 log-depth。让它保持恒定的深度是很困难的。我会考虑的。
这是另一个有趣的恶作剧,滥用重载决议。我们将把每个函数转换成另一个接受 rank
参数的函数,将我们所有的函数组合在一起,然后调用它们。选择会自然地从超载集中脱落。
我们的排名器就是这种类型的阶梯:
template <int I> struct rank : rank<I-1> { };
template <> struct rank<0> { };
我们需要一个函数转换器:
template <class T, class F>
auto prepend_arg(F&& f) {
return [f=std::forward<F>(f)](T, auto&&... args)
-> decltype(std::forward<F>(f)(std::forward<decltype(args)>(args)...))
{
return std::forward<F>(f)(std::forward<decltype(args)>(args)...);
};
}
然后:
template <std::size_t N, std::size_t... Is, std::size_t... Js,
class... X>
decltype(auto) apply_impl(std::index_sequence<Is...>,
std::index_sequence<Js...>,
X&&... x)
{
auto all = std::forward_as_tuple(std::forward<X>(x)...);
return overload(
// all of our function cases
prepend_arg<rank<N-Is>>(std::get<Is>(all))...,
// base case: do nothing
[](rank<0>, auto&&...) {}
)(rank<N>{}, std::get<N+Js>(all)...);
// ^^^^^^^^^^^^^^^^^^^^^^^^
// pass in all the arguments
}
template <std::size_t N, class... X>
decltype(auto) apply_on_validity(X&&... x) {
return apply_impl<N>(
std::make_index_sequence<N>{},
std::make_index_sequence<sizeof...(X)-N>{},
std::forward<X>(x)...);
}
我留下 overload()
作为练习。
使用boost::hana::overload_linearly
:
hana::overload_linearly(f1, f2, f3)(a, b, c, d, e)
如果 none 个表达式有效,这是一个编译错误,但在这种情况下很容易让它什么都不做:
hana::overload_linearly(f1, f2, f3, [](auto&&...) {})(a, b, c, d, e)
或者,使用 boost::hof::first_of
,其作用相同
之前的回答显示了如何根据调用的有效性应用函数:N
函数。
问题如下:
template <std::size_t N, class... X>
/* [Return type] */ apply_on_validity(X&&... x)
{
// Tricks here
}
// The function would be equivalent to a hypothetical
// template <std::size_t N, class... F, class... Args>
// [Return type] apply_on_validity(F&&... f, Args&&... args)
// where N = sizeof...(F) is the number of functions
在执行方面:
apply_on_validity<3>(f1, f2, f3, a, b, c, d, e);
会:
- 如果表达式有效则调用
f1(a, b, c, d, e)
,否则 - 如果表达式有效则调用
f2(a, b, c, d, e)
,否则 - 如果表达式有效则调用
f3(a, b, c, d, e)
,否则 - 什么都不做
在所有情况下,它都会 return 执行函数的结果。
在调用方,模板参数3
表示已经指定的函数个数
它在 C++14 中可行吗?如果可行,怎么做?
在c++14中:
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <std::size_t N, std::size_t... Js, typename Args>
auto apply_on_validity_impl(int, std::integral_constant<std::size_t, N>, std::index_sequence<Js...>, Args&& args)
{
// Nothing here
}
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
auto apply_on_validity_impl(int, std::integral_constant<std::size_t, I>, std::index_sequence<Js...>, Args&& args)
-> decltype(std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...))
{
return std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...);
}
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
decltype(auto) apply_on_validity_impl(char, std::integral_constant<std::size_t, I>, std::index_sequence<Js...> seq, Args&& args)
{
return apply_on_validity_impl<N>(0, std::integral_constant<std::size_t, I + 1>{}, seq, std::forward<Args>(args));
}
template <std::size_t N, typename... Args>
decltype(auto) apply_on_validity(Args&&... args)
{
return apply_on_validity_impl<N>(0, std::integral_constant<std::size_t, 0>{}, std::make_index_sequence<sizeof...(Args) - N>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
在c++17中:
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
decltype(auto) apply_on_validity_impl(std::index_sequence<Js...> seq, Args&& args)
{
if constexpr (I == N)
{
}
else if constexpr (std::is_invocable_v<std::tuple_element_t<I, Args>, std::tuple_element_t<Js + N, Args>...>)
{
return std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...);
}
else
{
return apply_on_validity_impl<N, I + 1>(seq, std::forward<Args>(args));
}
}
template <std::size_t N, typename... Args>
decltype(auto) apply_on_validity(Args&&... args)
{
return apply_on_validity_impl<N, 0>(std::make_index_sequence<sizeof...(Args) - N>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
您使用的语法有点笨拙,因为您必须确切地知道您有多少个函数。结合
// Would be a local class except for the fact that it needs
// to have templates, thus can't be local to the function.
template<class... Fs>
class apply_first_f final {
public:
apply_first_f(Fs&&... fs)
: fs_{ std::forward<Fs>(fs)... }
{}
template<class... Args>
decltype(auto) operator()(Args&&... args) const
{
return apply_impl<sizeof...(Args)>(std::make_index_sequence<sizeof...(Fs)>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
private:
std::tuple<std::decay_t<Fs>...> fs_;
template<size_t argsSize, size_t... Is, class Args>
decltype(auto) apply_impl(std::index_sequence<Is...>, Args&& args) const
{
return apply_on_validity_impl<sizeof...(Fs)>(
0,
std::integral_constant<size_t, 0>{},
std::make_index_sequence<argsSize>{},
std::tuple_cat(
std::forward_as_tuple(std::get<Is>(fs_)...),
std::forward<Args>(args)
)
);
}
};
template<class... Fs>
auto make_apply_first_valid_f(Fs&&... fs)
{
return apply_first_f<Fs...>{ std::forward<Fs>(fs)... };
}
函数可以这样使用:
make_apply_first_valid_f(f1, f2, f3)(args);
DEMO(改编自 Piotr 的演示)
将它与 Piotr 的 C++ 1z 示例一起使用只需要一个小改动:
template<class... Fs>
class apply_first_f final {
// ...
template<size_t argsSize, size_t... Is, class Args>
decltype(auto) apply_impl(std::index_sequence<Is...>, Args&& args) const
{
return apply_on_validity_impl<sizeof...(Fs), /* added */ 0>(
/* 0, */
/* std::integral_constant<size_t, 0>{}, */
std::make_index_sequence<argsSize>{},
std::tuple_cat(
std::forward_as_tuple(std::get<Is>(fs_)...),
std::forward<Args>(args)
)
);
}
// ...
};
我在之前的迭代中已经回答过这个问题,但是 nary 版本有一个错误,而 clang 有一个内部编译器错误,很难找到这个错误。
我已经修复了这个错误。所以这里就一堆元编程,后面解决你的问题。
首先,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-timeif dispatch machinery.
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。
现在我们希望能够调度一组可能性。为了简单起见(呵呵),我们将使用 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)>();
// some compilers dislike lambdas with unexpanded parameter packs.
// this helps with that:
auto helper = [&](auto I)->decltype(auto){
return std::get<decltype(I)::value>( std::forward_as_tuple( decltype(fs)(fs)... ) );
};
// Get 0 through N-1 as compile-time constants:
return indexer
(
[helper](auto...Is){
// make tuple of functions:
auto fs_tuple = std::forward_as_tuple( helper(Is)... );
// This is what is returned from the `auto_dispatch` function
// it perfect forwards into the correct lambda passed to `auto_dispatch`
// based on which is the first one which can be invoked by
// args...
return [fs_tuple](auto&&...args) {
// dispatcher knows which one can be called
auto dispatcher = dispatch(can_call<Fs(decltype(args)...)>{}...);
// here we get the first one that can be called, or an empty lambda:
auto&& f0 = dispatcher(std::get<decltype(Is)::value>(fs_tuple)...);
// here we do the actual call:
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" );
}
上面唯一的 N-ary 递归模板实例是 dispatch_index
。我可以通过一些工作(分而治之)来实现 log-depth。让它保持恒定的深度是很困难的。我会考虑的。
这是另一个有趣的恶作剧,滥用重载决议。我们将把每个函数转换成另一个接受 rank
参数的函数,将我们所有的函数组合在一起,然后调用它们。选择会自然地从超载集中脱落。
我们的排名器就是这种类型的阶梯:
template <int I> struct rank : rank<I-1> { };
template <> struct rank<0> { };
我们需要一个函数转换器:
template <class T, class F>
auto prepend_arg(F&& f) {
return [f=std::forward<F>(f)](T, auto&&... args)
-> decltype(std::forward<F>(f)(std::forward<decltype(args)>(args)...))
{
return std::forward<F>(f)(std::forward<decltype(args)>(args)...);
};
}
然后:
template <std::size_t N, std::size_t... Is, std::size_t... Js,
class... X>
decltype(auto) apply_impl(std::index_sequence<Is...>,
std::index_sequence<Js...>,
X&&... x)
{
auto all = std::forward_as_tuple(std::forward<X>(x)...);
return overload(
// all of our function cases
prepend_arg<rank<N-Is>>(std::get<Is>(all))...,
// base case: do nothing
[](rank<0>, auto&&...) {}
)(rank<N>{}, std::get<N+Js>(all)...);
// ^^^^^^^^^^^^^^^^^^^^^^^^
// pass in all the arguments
}
template <std::size_t N, class... X>
decltype(auto) apply_on_validity(X&&... x) {
return apply_impl<N>(
std::make_index_sequence<N>{},
std::make_index_sequence<sizeof...(X)-N>{},
std::forward<X>(x)...);
}
我留下 overload()
作为练习。
使用boost::hana::overload_linearly
:
hana::overload_linearly(f1, f2, f3)(a, b, c, d, e)
如果 none 个表达式有效,这是一个编译错误,但在这种情况下很容易让它什么都不做:
hana::overload_linearly(f1, f2, f3, [](auto&&...) {})(a, b, c, d, e)
或者,使用 boost::hof::first_of
,其作用相同