c++c++17structured-bindings

Lack of type information in structured bindings


I just learned about structured bindings in C++, but one thing I don't like about

auto [x, y] = some_func();

is that auto is hiding the types of x and y. I have to look up some_func's declaration to know the types of x and y. Alternatively, I could write

T1 x;
T2 y;
std::tie(x, y) = some_func();

but this only works, if x and y are default constructible and not const. Is there a way to write

const auto [x, y] = some_func();

for non-default-constructible types of x and y in a way that makes the types of x and y visible? The compiler should preferably complain when I declare x and y as something incompatible with some_func's return types, i.e. not const auto /* T1, T2 */ [x, y] = some_func();.


Clarification. Since the comments below my question seem to revolve around whether or not to use &, and some previous answers misunderstood my question as "which syntax to use to extract the returned pair's data type", I think I need to clarify my question.

Assume we have our code distributed in multiple files

//
// API.cpp
//
#include <utility>

class Foo {
public:
    Foo () {}
};

Foo foo;


class Bar {
private:
    Bar () {}
public:
    static Bar create () { return Bar(); }
};

Bar bar = Bar::create();


std::pair<int, bool> get_values () {
    return std::make_pair(73, true);
}

std::pair<Foo&, Bar&> get_objects () {
    return std::pair<Foo&, Bar&>(foo, bar);
}

//
// Program.cpp
//
int main (int, char**) {
    const auto [x, y] = get_values();
    const auto& [foo, bar] = get_objects();

    /* Do stuff with x, y, foo and bar */

    return 0;
}

At the time of writing this code the declarations of get_values and get_objects are fresh in my mind, so I know their return types. But when looking at Program.cpp one week later I barely remember the code in main let alone the data types of its variables or the return types of get_values and get_objects, so I need to open API.cpp and find get_values and get_objects to know their return types.

My question is whether there is a syntax to write the data types of the variables x, y, foo and bar in main into the structured binding? Preferably in a manner that allows the compiler to correct me, if I make mistakes, so no comments. Something along the lines of

int main (int, char**) {
    // Pseudo-Code
    [const int x, const bool y] = get_values();
    [const Foo& foo, const Bar& bar] = get_objects();
    /* Do stuff with x, y, foo and bar */
    return 0;
}

Solution

  • There is no mechanism to state the types of "variables" in a structured binding declaration. If you want the type names to be visible, you have to forgo the convenience of structured binding declarations.

    This is important because of how structured binding works. x and y aren't really variables per se. They're stand-ins for accessing the components of the object that the structured binding statement stored. They're components of the object that was captured by the declaration. There is only one actual variable: the unnamed variable that is auto-deduced. The names you declare are just components of that object.

    Understanding this, now consider this statement: int i = expr; This code works so long as expr is something that can be converted to an int.

    If you could put type names in structured binding declarations, people would have the same expectation. They would expect that if a function returns a tuple<float, int>, they could capture this in an auto [int x, int y]. But they can't, because the object being stored is a tuple<float, int>, and its first member is a float. The compiler would have to invent some new object that contains two ints and do a conversion.

    But that's dangerous, particularly when dealing with return values that contain references. You can theoretically turn a tuple<float&, int> into a tuple<int, int>. But it wouldn't have the same meaning, since you cannot modify the object being referenced.

    But again, users expect variable declarations to be able to do such conversions. Users rely on it all the time. Taking that power away would serve only to create confusion in the feature.

    So the feature doesn't do it.