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<class... Ts, class F>
auto unpack( std::tuple<Ts...>&& 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::future
s 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<std::tuple>
. 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 tuple
s), 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 <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();
}
this will let you do the following:
LoadWaveFront("assets/teapot.obj")
*then* [&]( VAO teapotVAO, Mesh teapotMesh, ShaderProgram teapotShader ){
// code
}
which I find cute.