Choose function to apply based on the validity of an expression

前端 未结 4 1581
自闭症患者
自闭症患者 2021-01-01 17:28

The problem is the following, in C++14:

  • Let\'s have two functions FV&& valid_f, FI&& invalid_f, and argu
相关标签:
4条回答
  • 2021-01-01 17:44

    Piotr Skotnicki's answer 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*This version creates more warnings but is simpler

    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";
        }
    }
    
    0 讨论(0)
  • 2021-01-01 17:45

    First, a homebrew version of C++2a's 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;
    

    As it happens, std::result_of_t is the trait we want to test.

    template<class Sig>
    using can_call = can_apply< std::result_of_t, Sig >;
    

    now can_call< Some(Sig,Goes,Here) > is true_type iff the expression you want can be called.

    Now we write some compile-time if 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 a lambda. The first of the SomeBools which is compile-time truthy (has a ::value that evaluates to true in a boolean context) determines what the returned lambda does. Call that the dispatch index.

    It returns the dispatch_index'd argument to the next call, and an empty lambda if that is one-past-the-end of the list.

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

    and done, live example.

    We could make this generic to enable nary version. First 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>{} );
    }
    

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

    with test code:

    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

    0 讨论(0)
  • 2021-01-01 17:46

    Take:

    template <int N> struct rank : rank<N-1> {};
    template <> struct rank<0> {};
    

    and then:

    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

    0 讨论(0)
  • 2021-01-01 17:49

    Here's an alternative answer, just for kicks. We need a 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); }
    

    And an is_callable. Since you're just supporting functions, we can do it as:

    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
    { };
    

    And then we can construct the logic in place:

    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)...);
    }
    
    0 讨论(0)
提交回复
热议问题