C++11 pattern for factory function returning tuple

前端 未结 4 687
感动是毒
感动是毒 2020-12-30 07:18

In my project I have some functions like

std::tuple LoadWavefront(std::string filename);

That I can use lik

4条回答
  •  天涯浪人
    2020-12-30 08:00

    There is an inverted-control flow style that could be useful.

    LoadWavefront("assets/teapot.obj", [&]( VAO teapotVAO, Mesh teapotMesh, ShaderProgram teapotShader ){
      // code
    });
    

    with VAO& reference-style instead optional. In this case, the return value of the lambda could be used as the return value of the LoadWavefront, with a default lambda that just forwards all 3 arguments out allowing "old style" access if you want. If you only want one, or want to do some stuff after it is loaded, you can also do that.

    Now, LoadWavefront should probably return a future as it is an IO function. In this case, a future of tuple. We can make the above pattern a bit more generic:

    template
    auto unpack( std::tuple&& tup, F&& f ); // magic
    

    and do

    unpack( LoadWavefront("assets/teapot.obj"), [&]( VAO teapotVAO, Mesh teapotMesh, ShaderProgram teapotShader ){
      // code
    });
    

    unpack can also be taught about std::futures and automatically create a future of the result.

    This can lead to some annoying levels of brackets. We could steal a page from functional programming if we want to be insane:

    LoadWavefront("assets/teapot.obj")
    *sync_next* [&]( VAO teapotVAO, Mesh teapotMesh, ShaderProgram teapotShader ){
      // code
    };
    

    where LoadWavefront returns a std::future. The named operator *sync_next* takes a std::future on the left hand side and a lambda on the right hand side, negotiates a calling convention (first trying to flatten tuples), and continues the future as a deferred call. (note that on windows, the std::future that async returns fails to .wait() on destruction, in violation of the standard).

    This is, however, an insane approach. There may be more code like this coming down the type with the proposed await, but it will provide much cleaner syntax to handle it.


    Anyhow, here is a complete implementation of an infix *then* named operator, just because live example

    #include 
    #include 
    #include 
    #include 
    
    // a better std::result_of:
    template
    struct invoke_result {};
    template
    struct invoke_result()(std::declval()...)))>
    {
      using type = decltype(std::declval()(std::declval()...));
    };
    template
    using invoke_t = typename invoke_result::type;
    
    // complete named operator library in about a dozen lines of code:
    namespace named_operator {
      templatestruct make_operator{};
    
      template struct half_apply { T&& lhs; };
    
      template
      half_apply operator*( Lhs&& lhs, make_operator ) {
          return {std::forward(lhs)};
      }
    
      template
      auto operator*( half_apply&& lhs, Rhs&& rhs )
      -> decltype( invoke( std::forward(lhs.lhs), Op{}, std::forward(rhs) ) )
      {
          return invoke( std::forward(lhs.lhs), Op{}, std::forward(rhs) );
      }
    }
    
    // create a named operator then:
    static struct then_t:named_operator::make_operator {} then;
    
    namespace details {
      template
      auto invoke_helper( std::index_sequence, Tup&& tup, F&& f )
      -> invoke_t::type...)>
      {
          return std::forward(f)( std::get(std::forward(tup))... );
      }
    }
    
    // first overload of A *then* B handles tuple and tuple-like return values:
    template
    auto invoke( Tup&& tup, then_t, F&& f )
    -> decltype( details::invoke_helper( std::make_index_sequence< std::tuple_size>{} >{}, std::forward(tup), std::forward(f) ) )
    {
      return details::invoke_helper( std::make_index_sequence< std::tuple_size>{} >{}, std::forward(tup), std::forward(f) );
    }
    
    // second overload of A *then* B
    // only applies if above does not:
    template
    auto invoke( T&& t, then_t, F&& f, ... )
    -> invoke_t< F(T) >
    {
      return std::forward(f)(std::forward(t));
    }
    // support for std::future *then* lambda, optional really.
    // note it is defined recursively, so a std::future< std::tuple >
    // will auto-unpack into a multi-argument lambda:
    template
    auto invoke( std::future x, then_t, F&& f )
    -> std::future< decltype( std::move(x).get() *then* std::declval() ) >
    {
      return std::async( std::launch::async,
        [x = std::move(x), f = std::forward(f)]() mutable {
          return std::move(x).get() *then* std::move(f);
        }
      );
    }
    
    int main()
    {
      7
      *then* [](int x){ std::cout << x << "\n"; };
    
      std::make_tuple( 3, 2 )
      *then* [](int x, int y){ std::cout << x << "," << y << "\n"; };
    
      std::future later =
        std::async( std::launch::async, []{ return 42; } )
        *then* [](int x){ return x/2; }
        *then* [](int x){ std::cout << x << "\n"; };
      later.wait();
    }
    

    this will let you do the following:

    LoadWaveFront("assets/teapot.obj")
    *then* [&]( VAO teapotVAO, Mesh teapotMesh, ShaderProgram teapotShader ){
      // code
    }
    

    which I find cute.

提交回复
热议问题