返回元组的工厂函数的C++ 11模式

Dan*_*Dan 13 c++ factory c++11

在我的项目中,我有一些功能

std::tuple<VAO, Mesh, ShaderProgram> LoadWavefront(std::string filename);
Run Code Online (Sandbox Code Playgroud)

我可以像这样使用:

VAO teapotVAO;
Mesh teapotMesh;
ShaderProgram teapotShader;
std::tie(teapotVAO, teapotMesh, teapotShader)
    = LoadWavefront("assets/teapot.obj");
Run Code Online (Sandbox Code Playgroud)

问题是,这要求每个类都有一个默认构造函数,它在无效状态下创建它们,这很容易出错.如何在不必std::get<>每件物品的情况下解决这个问题?有一种优雅的方式来做到这一点?

Yak*_*ont 17

有一种倒置控制流式可能很有用.

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

使用VAO&引用样式而不是可选.在这种情况下,lambda的返回值可以用作返回值,LoadWavefront默认的lambda只是转发所有3个参数,如果你愿意,允许"旧式"访问.如果你只想要一个,或者想要在加载后做一些东西,你也可以这样做.

现在,LoadWavefront应该返回一个,future因为它是一个IO函数.在这种情况下,一个futuretuple.我们可以使上面的模式更通用:

template<class... Ts, class F>
auto unpack( std::tuple<Ts...>&& tup, F&& f ); // magic
Run Code Online (Sandbox Code Playgroud)

并做

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

unpack也可以讲授std::futures并自动创建结果的未来.

这可能会导致一些恼人的括号.如果我们想要疯狂,我们可以从函数式编程中窃取一个页面:

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

哪里LoadWavefront返回一个std::future<std::tuple>.命名的操作*sync_next*需要std::future在左侧和右侧拉姆达协商调用约定(第一次试图拉平tupleS),并继续future作为递延电话.(请注意,在Windows上std::future,async返回时无法.wait()破坏,违反标准).

然而,这是一种疯狂的做法.可能会有更多这样的代码与提议的类型相关await,但它将提供更清晰的语法来处理它.


无论如何,这里是一个*then*名为operator 的infix的完整实现,只是因为实例

#include <utility>
#include <tuple>
#include <iostream>
#include <future>

// a better std::result_of:
template<class Sig,class=void>
struct invoke_result {};
template<class F, class... Args>
struct invoke_result<F(Args...), decltype(void(std::declval<F>()(std::declval<Args>()...)))>
{
  using type = decltype(std::declval<F>()(std::declval<Args>()...));
};
template<class Sig>
using invoke_t = typename invoke_result<Sig>::type;

// complete named operator library in about a dozen lines of code:
namespace named_operator {
  template<class D>struct make_operator{};

  template<class T, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, Op> operator*( Lhs&& lhs, make_operator<Op> ) {
      return {std::forward<Lhs>(lhs)};
  }

  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, Op>&& lhs, Rhs&& rhs )
  -> decltype( invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
  {
      return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

// create a named operator then:
static struct then_t:named_operator::make_operator<then_t> {} then;

namespace details {
  template<size_t...Is, class Tup, class F>
  auto invoke_helper( std::index_sequence<Is...>, Tup&& tup, F&& f )
  -> invoke_t<F(typename std::tuple_element<Is,Tup>::type...)>
  {
      return std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... );
  }
}

// first overload of A *then* B handles tuple and tuple-like return values:
template<class Tup, class F>
auto invoke( Tup&& tup, then_t, F&& f )
-> decltype( details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) ) )
{
  return details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}

// second overload of A *then* B
// only applies if above does not:
template<class T, class F>
auto invoke( T&& t, then_t, F&& f, ... )
-> invoke_t< F(T) >
{
  return std::forward<F>(f)(std::forward<T>(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<class X, class F>
auto invoke( std::future<X> x, then_t, F&& f )
-> std::future< decltype( std::move(x).get() *then* std::declval<F>() ) >
{
  return std::async( std::launch::async,
    [x = std::move(x), f = std::forward<F>(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<void> later =
    std::async( std::launch::async, []{ return 42; } )
    *then* [](int x){ return x/2; }
    *then* [](int x){ std::cout << x << "\n"; };
  later.wait();
}
Run Code Online (Sandbox Code Playgroud)

这将让您执行以下操作:

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

我觉得很可爱


utn*_*tim 4

如何解决这个问题而不必 std::get<> 每个项目?有没有一种优雅的方法来做到这一点?

按值返回,而不是按“值”返回(这是 std::tuple 允许您执行的操作)。

API变更:

class Wavefront
{
public:
    Wavefront(VAO v, Mesh m, ShaderProgram sp); // use whatever construction
                                                // suits you here; you will
                                                // only use it internally
                                                // in the load function, anyway
    const VAO& vao() const;
    const Mesh& mesh() const;
    const ShaderProgram& shader() const;
};

Wavefront LoadWavefront(std::string filename);
Run Code Online (Sandbox Code Playgroud)