С++ 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<>
каждого элемента? Есть ли элегантный способ сделать это?
Ответы
Ответ 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");