【问题标题】:Choose function to apply based on the validity of an expression根据表达式的有效性选择要应用的函数
【发布时间】:2017-06-21 04:02:09
【问题描述】:

问题如下,在C++14:

  • 让我们有两个函数FV&& valid_fFI&& 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
}

但我真的不知道该怎么做。有什么想法吗?


链接到泛化here

【问题讨论】:

  • 您可以考虑查看void_t 并将SFINAE 基于void_t 表达式。
  • 但是当valid_finvalid_fapply_on_validity() 的参数时,你想要这个吗?没有两个已知和固定的功能?
  • 不能等待 if constexpr...
  • @Barry 也一样 :)
  • @Barry 你是什么意思,等等?它已经在 apt.llvm.org 的 clang 3.9 和主干的 gcc SVN 提示中(Ubuntu 17.04 测试发行版有 .deb 包)

标签: c++ templates c++14 overloading template-meta-programming


【解决方案1】:

拍摄:

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

DEMO

【讨论】:

  • 一点解释:decltype 使用 SFINAE 来确保对指定表达式启用模板功能。 rank 参数用于消除歧义并确定重载的优先级,以防多个模板函数重载有效。
  • 好吧,我刚刚完成了我的解决方案,发现您发布的正是我想出的内容。
【解决方案2】:

Piotr Skotnicki's answer 非常棒,但是这样的代码让我不得不指出由于constexpr if 和其他类型特征(如is_callable),C++17 将变得多么干净: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";
    }
}

【讨论】:

  • 在大多数情况下,您可以将那些复杂的 is_callable_v 检查替换为 is_callable_v&lt;FV(Args...)&gt; 什么时候不是这种情况?我相信这些在这种情况下是完全等价的(即,当用于从转发引用推导出的类型时)。
  • @Barry 和 yuri Kilochek:当我写这篇文章时,我最初想到的是 ref 限定函子,但似乎 is_callable 已经涵盖了它。现在 gcc 喜欢给我很多警告,但它有效
【解决方案3】:

这是一个替代答案,只是为了好玩。我们需要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)...);
}

【讨论】:

【解决方案4】:

首先,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 如果你想要的表达式可以被调用。

现在我们编写一些编译时调度机制。

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... ) 返回一个 lambda。 SomeBools 中的第一个是编译时真值(具有在布尔上下文中计算为 true 的 ::value)确定返回的 lambda 做什么。称之为调度索引。

它将 dispatch_index 的参数返回给下一次调用,如果是列表的最后一个,则返回一个空的 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" );
}

Live example

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-16
    • 2013-07-23
    • 1970-01-01
    • 2015-07-23
    • 2012-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多