C++17: Structure binding

Even when the new C++ standard was a work in progress most major compilers have been supporting the upcoming C++17 features already, now the heavy lifting of the job has been completed and just some tweaks here and there are separating us from the “official” publication of the ISO Standard, it’s a great time to publish yet another post about what’s new in C++17!

And today I’m going to show you what structure binding is all about!

Well, structure binding –as the name might suggest– is a cool functionality which allows to easy bind variables to member of structs, but also to arrays, tuples –or similar– types and in general to various “stuff“. Let’s have a look at a couple of examples.

Structure binding

It’s not infrequent that we might want to copy values from a structure –class– into local variables to use within a function, is even more frequent that we might want to have references to those member variables; this is normally accomplished by declaring local variables –or refs– and assigning to them the variables from the struct:


//What a beautiful structure!
struct my_struct {
	int i;
	double d;
	char c;
	std::string str;

	my_struct() :
		i{69},
		d{3.14},
		c{'a'},
		str{"Hello"}
	{}
}
my_struct get_stuff()
{
	return my_struct();
}
//"Realistic" example of how use my_struct
void bee()
{
	auto that_struct = get_stuff();
	//Old school way of getting refs to members
	auto& ir = that_struct.i;
	auto& strref = that_struct.str;
	//Modify and print
	ir *= 2;
	std::reverse( strref.begin(), strref.end() );
}

This code example is pretty reasonable, although a bit ugly –but who cares??-. The point is that we’re used to declare ref variables for each individual member we want to be referring to, if as in this case we have four members then we potentially need to have four refs variables.

Structure binding now, allows a huge simplification of bee:

void bee()
{
	auto that_struct = my_struct();

	auto& [ir, dr, cr, strref] = that_struct;
			
	//Modify and print
	ir *= 2;
	std::reverse( strref.begin(), strref.end() );
}

What is happening here is that we’re declaring four reference variables for which the types are auto-deduced and with the names in the square brackets! How easy is that?

One can bind r-values as well:

void bee()
{
	auto&& [i, d, c, s] = get_stuff();
	if ( i > 0 ) { //Some realistic usage of my_struct
		std::cout << s << std::endl;
	}
}

In general you should refer to the auto deduction rule to understand what is going on with those variables, this whole feature is all about hiding some noisy typing by letting the compiler generate for you variables and references.

There are some caveats you should remember, the structure binding deduction works indiscriminately for all the variables in the structure you want to bind, if you have four member variable then you shall have four variables names listed in the square bracket:

void bee()
{
	auto&& [i, d, s] = get_stuff();
	if ( i > 0 ) { //Some realistic usage of my_struct
		std::cout << s << std::endl;
	}
}

This code is generating the following error with GCC:

A similar error pop up in case you attempt to provide more variables that members in the struct.

For-loop structure binding

This is a powerful use case, it make it possible to declare on-the-fly multiple variables inside a range-based loop declaration, the types of the variables will be deduced using the usual auto rules:

struct data {
	int value;
	std::string str;
	double another_value;
};
std::vector< data > lot_of_data = {
	{ 69, "Hey!", 3.14 },
	{ 70, "Hello", 2.72 },
	{ -1, "Worls", 1.61 }
	//tons of other stuff...
};

//Structure binding for-loops
for ( auto& [v, s, v2] : lot_of_data ) {
	std::cout << v << "," << s << "," << v2 << std::endl;
}

This code prints out exactly what you might expect from it, pretty nice right? This works also with any other type of structure, yet another trivial example might be:

std::map< std::string, int > even_more_data = {
	{ "Hello", 10 },
	{ "World", 11 },
	//...
};

for ( auto& [str, idx] : even_more_data ) {
	//Serious usage
}

Structure binding and Tuple!

I suspect that the whole idea of structure binding came out after the complain of people that were continuously getting upset on how ugly it is to get stuff out of a std::tuple, yeah that’s right! You know already what I mean: std::tie.

std::tuple< int, double, char> bar()
{
	return { 69, 3.14, 'a'};
}
void foo()
{
	int x;
	double d;
	char c;
	std::tie( x, d, c ) = bar();
	std::cout << x << "," << d << "," << c << std::endl;
}

You can’t do much better than this to extract those values from a tuple, and the worst thing is that there’s no way of extracting a reference to values in a tuple, std::tie do not support such thing. In C++17 things got a little bit prettier:

void foo()
{
	auto [x, d, c] = bar();
	std::cout << x << "," << d << "," << c << std::endl;
}

This foo looks much better than the other foo don’t you think? But let’s came back for a second to that reference problem I was writing few lines back, how to get refs to the stuff inside a std::tuple? Before C++17 we had to write something like this:

void foo()
{
	auto tup = bar();
	print_tuple( tup );
	//Reference to the tuple stuff
	auto& ref_i = std::get<0>( tup );
	auto& ref_d = std::get<1>( tup );
	ref_i *= 2;
	ref_d *= 2;
	print_tuple( tup );
}

Which involve extracting the variables you want to access one by one with std::get, that certainly work, but we can save some typing now:

void foo()
{
	auto tup = bar();
	print_tuple( tup );
	auto& [ref_i, ref_d, ref_c] = tup;
	ref_i *= 2;
	ref_d *= 2;
	print_tuple( tup );
}

Much better don’t you think? In both cases the output is:

To close this tuple paragraph, here’s the print_tuple function –just in case you need one– :

template<std::size_t> struct mint {};

template<typename TUP, std::size_t idx>
void print_tup_( const TUP& tp, mint<idx> )
{
	std::cout << std::get < std::tuple_size<TUP>::value - idx > ( tp ) << ",";
	print_tup_( tp, mint < idx - 1 > () );
}

template<typename TUP>
void print_tup_( const TUP& tp, mint<1> )
{
	std::cout << std::get < std::tuple_size<TUP>::value - 1 > ( tp ) << std::endl;
}

template<typename...ARGS>
void print_tuple( const std::tuple<ARGS...>& tp )
{
	print_tup_( tp, mint<sizeof...( ARGS )>() );
}

Conclusion

This is not –only– syntactical sugar, no! Don’t say that! There’s some real value given by this structure binding declaration mechanism, try by yourself and you’ll see!

thanks for reading, see you next time!

Leave a Reply