Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a new variable and use it in std::tie at the same time?

Tags:

c++

c++11

c++14

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.

like image 468
wyer33 Avatar asked Mar 28 '15 00:03

wyer33


2 Answers

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.

like image 73
fireboot Avatar answered Nov 01 '22 17:11

fireboot


@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.

like image 32
wyer33 Avatar answered Nov 01 '22 16:11

wyer33