Apply the first valid function of a set of N functions

前端 未结 5 2036
南旧
南旧 2020-12-10 06:52

This previous answer shows how to apply function based on the validity of a call: here. However, it applies to two functions. I was wondering if the concept could be general

5条回答
  •  暖寄归人
    2020-12-10 07:29

    I had answered this in the previous iteration, but the nary version had a bug, and clang had an internal compiler error which made it hard to find the bug.

    I have since fixed the bug. So here is a pile of metaprogramming, followed by solving your problem.

    First, a homebrew version of C++2a's is_detected:

    #include 
    #include 
    #include 
    #include 
    
    namespace details {
      templateusing void_t=void;
      templateclass Z, class=void, class...Ts>
      struct can_apply:std::false_type{};
      templateclass Z, class...Ts>
      struct can_apply>, Ts...>:std::true_type{};
    }
    templateclass Z, class...Ts>
    using can_apply = typename details::can_apply::type;
    

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

    template
    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
    using index_t=std::integral_constant;
    template
    constexpr index_t index_v{};
    
    constexpr inline index_t<0> dispatch_index() { return {}; }
    template =0
    >
    constexpr index_t<0> dispatch_index( B0, Bs... ) { return {}; }
    template =0
    >
    constexpr auto dispatch_index( B0, Bs... ) { 
      return index_v< 1 + dispatch_index( Bs{}...) >;
    }
    
    template
    auto dispatch( Bs... ) {
      using I = decltype(dispatch_index( Bs{}... ));
      return [](auto&&...args){
        return std::get( 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.

    Now we want to be able to dispatch on a set of possibilities. To make this simple (heh), we'll use index_over:

    template
    auto index_over( std::index_sequence ){
      return [](auto&&f)->decltype(auto){
        return decltype(f)(f)( std::integral_constant{}... );
      };
    }
    template
    auto index_over(std::integral_constant ={}){
      return index_over(std::make_index_sequence{} );
    }
    

    which lets us expand parameter packs without having to build new functions.

    Then we can write auto_dispatch as a single function template:

    template
    auto auto_dispatch( Fs&&... fs ) {
      auto indexer =  index_over();
      // some compilers dislike lambdas with unexpanded parameter packs.
      // this helps with that:
      auto helper = [&](auto I)->decltype(auto){ 
        return std::get( 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{}...);
            // here we get the first one that can be called, or an empty lambda:
            auto&& f0 = dispatcher(std::get(fs_tuple)...);
            // here we do the actual call:
            std::forward(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

    The only N-ary recursive template instantiation above is dispatch_index. I can make that log-depth with a bit of work (divide and conquer). Getting it constant depth is hard. I will think on it.

提交回复
热议问题