Подтвердить что ты не робот

С++ 11 для функции factory функция возврата кортежа

В моем проекте у меня есть некоторые функции, такие как

std::tuple<VAO, Mesh, ShaderProgram> LoadWavefront(std::string filename);

Что я могу использовать следующим образом:

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

Проблема заключается в том, что для каждого из этих классов требуется конструктор по умолчанию, который создает их в недопустимом состоянии, которое подвержено ошибкам. Как мне обойти это без необходимости std::get<> каждого элемента? Есть ли элегантный способ сделать это?

4b9b3361

Ответ 1

Как мне обойти это без использования 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);

Ответ 2

Существует стиль потока с инвертированным управлением, который может быть полезен.

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

с VAO& ссылочным стилем вместо необязательного. В этом случае возвращаемое значение лямбда может быть использовано как возвращаемое значение LoadWavefront, с лямбдой по умолчанию, которая просто переадресует все 3 аргумента, позволяя вам доступ к "старому стилю", если вы хотите. Если вы хотите только один или хотите сделать некоторые вещи после его загрузки, вы также можете это сделать.

Теперь LoadWavefront должен, вероятно, вернуть future, поскольку это функция ввода-вывода. В этом случае a future of tuple. Мы можем сделать приведенный выше шаблон более общим:

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

и do

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

unpack также можно узнать о std::future и автоматически создать будущее результата.

Это может привести к некоторым раздражающим уровням скобок. Мы могли бы украсть страницу из функционального программирования, если хотим быть безумными:

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

где LoadWavefront возвращает a std::future<std::tuple>. Именованный оператор *sync_next* принимает std::future с левой стороны и лямбда с правой стороны, согласовывает соглашение о вызове (сначала пытается сгладить tuple s) и продолжает future как отложенный вызов, (обратите внимание, что в окнах std::future, который async возвращается с ошибкой .wait() при уничтожении, в нарушение стандарта).

Это, однако, безумный подход. Там может быть больше кода, подобного этому, с типом с предлагаемым await, но он обеспечит гораздо более чистый синтаксис для его обработки.


Во всяком случае, вот полная реализация оператора infix *then* named, только потому, что живой пример

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

это позволит вам сделать следующее:

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

который я нахожу симпатичным.

Ответ 3

Вы можете использовать boost::optional:

boost::optional<VAO> teapotVAO;
boost::optional<Mesh> teapotMesh;
boost::optional<ShaderProgram> teapotShader;
std::tie(teapotVAO, teapotMesh, teapotShader)
    = LoadWavefront("assets/teapot.obj");

Конечно, вам придется изменить способ доступа к этим значениям, чтобы всегда делать *teapotVAO, но, по крайней мере, компилятор сообщит вам, испортил ли вы какой-либо доступ.

Ответ 4

Давайте еще дальше и предположим, что для этих классов нет конструктора по умолчанию.

Один из вариантов:

auto tup = LoadWavefront("assets/teapot.obj");
VAO teapotVAO(std::move(std::get<0>(tup)));
Mesh teapotMesh(std::move(std::get<1>(tup)));
ShaderProgram teapotShader(std::move(std::get<2>(tup)));

Это все еще остается вокруг tup в основном очищенном opject, который меньше идеала.

Но подождите... почему эти люди даже нуждаются в владении?

auto tup = LoadWavefront("assets/teapot.obj");
VAO& teapotVAO=std::get<0>(tup);
Mesh& teapotMesh=std::get<1>(tup);
ShaderProgram& teapotShader=std::get<2>(tup);

Пока ссылки находятся в той же области, что и возвращаемый кортеж, здесь нет проблем.

Лично это кажется ясным местом, где вместо этой бессмыслицы следует использовать набор интеллектуальных указателей:

LoadWavefront(const char*,std::unique_ptr<VAO>&,std::unique_ptr<Mesh>&,std::unique_ptr<ShaderProgram>&);

std::unique_ptr<VAO> teapotVAO;
std::unique_ptr<Mesh> teapotMesh;
std::unique_ptr<ShaderProgram> teapotShader;
LoadWavefront("assets/teapot.obj",teapotVAO,teapotMesh,teapotShader);

Это позаботится о проблеме собственности и позволит получить разумное нулевое состояние.

Изменить:/u/dyp указал, что вы можете использовать следующее с исходным стилем вывода

std::unique_ptr<VAO> teapotVAO;
std::unique_ptr<Mesh> teapotMesh;
std::unique_ptr<ShaderProgram> teapotShader;
std::tie(teapotVAO,teapotMesh,teapotShader) = LoadWavefront("assets/teapot.obj");