应用一组 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);

会:

在所有情况下,它都会 return 执行函数的结果。

在调用方,模板参数3表示已经指定的函数个数

它在 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)...));
}

DEMO

中:

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

DEMO 2

您使用的语法有点笨拙,因为您必须确切地知道您有多少个函数。结合 ,这解决了这个问题:

// 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" );
}

Live example

上面唯一的 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,其作用相同