Is there a good way to use std::tie
and create a new variable in one go? In other words, if a function returns an std::tuple
and we want to ultimately break up the result into individual components, is there a way to do these assignments without defining variables beforehand?
For example, consider the following code:
#include <tuple>
struct Foo {
Foo(int) {}
};
struct Bar{};
std::tuple <Foo,Bar> example() {
return std::make_tuple(Foo(1),Bar());
}
int main() {
auto bar = Bar {};
// Without std::tie
{
auto foo_bar = example();
auto foo = std::get<0>(std::move(foo_bar));
bar = std::get<1>(std::move(foo_bar));
}
// With std::tie
#if 0
{
// Error: no default constructor
Foo foo;
std::tie(foo,bar) = example();
}
#endif
}
Basically, the function example
returns a tuple. We already have a variable of type Bar
that we want to assign into, but we need a new variable of type Foo
. Without std::tie
, we don't need to create a dummy instance of Foo
, but the code requires us to put everything into a std::tuple
first and then divide it. With std::tie
, we have to allocate a dummy Foo
first, but we don't have a default constructor to do so. Really, we're pretending that the constructors for Foo
are complicated, so creating a dummy value first is undesirable. Ultimately, we'd just like to assign into both foo
and bar
, but want to do this assignment and allocate memory for Foo
at the same time.
This feature is called structured bindings in C++17. Very welcome addition!
Sample usage:
#include <iostream>
#include <tuple>
int main()
{
auto tuple = std::make_tuple(1, 'a', 2.3);
// unpack the tuple into individual variables declared at the call site
auto [ i, c, d ] = tuple;
std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';
return 0;
}
Tested in GCC 7.2 with -std=c++17
.
@MikaelPersson had the right link. Basically, there's no great way to do this. Though, there are some clever ways based on N3802. Namely, use
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
Then, write
// With compose
{
auto foo = apply([&bar](auto && foo,auto && bar_) {
bar=std::move(bar_);
return std::move(foo);
}, example());
}
And, yes, this whole thing is ugly, but the situation did come up in some instance I had. Nevertheless, as @MikaelPersson's link shows, this is a general issue and not one fully resolved yet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With