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;
}
CodePudding user response:
There is no mechanism to state the types of the "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 typenames 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 int
s 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.
CodePudding user response:
If you want to preserve structured binding and possible optimisations which comes with it, easiest way would be to put a comment denoting types. Obviously it would be bad if return types were to change: comment would become misleading. When writing types manually this would lead to a compile-time error.
To mimic this behavior, you can force a compile-time error manually:
#include <type_traits>
auto [x, y] = some_func();
static_assert(std::is_same_v<decltype(x), const my_type>);
static_assert(std::is_same_v<decltype(y), some_other__type>);
It will show type information, force a compile-time error if type would ever happens to be not what you expect and even prevent undesired conversions from happening. No more accidentally assigning long
to int
.
Alternatively: use IDE which can display local variables and their type.
CodePudding user response:
You may do this:
#include <iostream>
#include <tuple>
auto func( )
{
int x = 5;
double y = 5.5;
std::cout << "Executing the func..." << '\n';
return std::make_tuple<int, double>( std::move( x ), std::move( y ) );
}
int main()
{
const auto returnVal = func( );
const int& x = std::get<0>( returnVal );
// const unsigned int& x = std::get<0>( returnVal ); // replace this with the upper one and
// it will provoke a compile-time error
const double& y = std::get<1>( returnVal );
// const char& y = std::get<1>( returnVal ); // replace this with the upper one and
// it will provoke a compile-time error
static_assert( std::is_same_v< decltype( x ), decltype( std::get<0>( returnVal ) ) > &&
std::is_same_v< decltype( y ), decltype( std::get<1>( returnVal ) ) >, "Wrong types used" );
std::cout << x << " " << y << '\n';
return 0;
}
As you can see, this can be a replacement for structured bindings. x
and y
are references to the returned value. And you do explicitly see their types when reading the source code. And in case you declare them with wrong types then the compiler will complain (e.g. const char& y
instead of const double& y
).