I'm writing an API for a Machine Learning, and I need to have an overloaded function that can either receive a vector as an argument, or a vector of vectors (for a batch job).
I'm a having a bit of a problem with calling the function though.
As an simpler example, the function might look like this:
void bar( const std::vector<float>& arg ) {
std::cout << "BAR: Vector of float" << std::endl;
}
void bar( const std::vector<std::vector<float>>& arg ) {
std::cout << "BAR: Vector of vectors of float" << std::endl;
}
So, I'd expect that I could call it like this:
bar( { 1,2,3 } );
bar( { { 1,2,3 } } );
But on the second one the IDE complains that, BOTH overloaded functions match the argument list, and so I have to call it like this for it to work.
bar( { { { 1,2,3 } } } );
Why is that? Wouldn't that be a vector of vectors of vectors (i.e. a "3D-vector")?
The same is when I'm passing a previously initialized vector:
std::vector<float> v = { 1,2,3,4,5 };
bar( v );
bar( { v } );
Both print out the BAR: Vector of float
message. So I now have to go:
bar( { { { v } } } );
for it to work, and this now looks like a 4D-vector. Am I missing something?
Welcome to brace hell. When you have
bar( { 1,2,3 } );
the { 1,2,3 }
is treated as a std::initializer_list<float>
and the only viable function to call is void bar( const std::vector<float>& arg )
When you have
bar( { { 1,2,3 } } );
Now { { 1,2,3 } }
can be construed as the outer braces denoting a std::vector<float>
, and the inner braces the std::initializer_list<float>
for it, or could be the construction of a std::initializer_list<std::initializer_list<float>>
to be used to construct the 2d vector. Either option is just as good, so you get an ambiguity. As yo found, the solution is
bar( { { { 1,2,3 } } } );
So now the outer most set of braces denotes creating a std::vector<std::vector<float>>
and the second outer most braces denote the start of a std::initializer_list<std::initializer_list<float>>
and the inmost braces being an element of that.
With
bar( v );
bar( { v } );
It is a little more complicated. Obviously bar( v );
will do what you want, but bar( { v } );
actually works unlike bar( { { 1,2,3 } } );
because of the rules in [over.ics.list]. Specifically, paragraph 7 says that { v }
is an exact match for creating a std::vector<float>
via the copy constructor while creating a std::vector<std::vector<float>>
is a user defined conversion. This means that calling void bar( const std::vector<float>& arg )
is a better match and that is what you see. You need to use
bar( { { { v } } } );
so that the outer set of braces denotes the std::vector<std::vector<float>>
and the middle set is the start of the std::initializer_list<std::vector<float>>
, and the innermost set is the single std::vector<float>
element of that list.
The answer by NathanOliver explains the reasons of the ambiguity.
If you are interested in distinguish all those cases, you should add other overloads:
#include <initializer_list>
#include <iostream>
#include <vector>
void bar( std::vector<float> const& arg ) {
std::cout << "BAR: Vector of float, size " << arg.size() << '\n';
}
void bar( std::vector<std::vector<float>> const& arg ) {
std::cout << "BAR: Vector of vectors of float, size " << arg.size() << '\n';
}
void bar( std::initializer_list<float> lst ) {
std::cout << "BAR: Initializer list of float, size " << lst.size() << '\n';
}
void bar( std::initializer_list<std::initializer_list<float>> lst ) {
std::cout << "BAR: Initializer list of initializer list of float, size "
<< lst.size() << '\n';
}
void bar( std::initializer_list<std::vector<float>> lst ) {
std::cout << "BAR: Initializer list of vector of float, size "
<< lst.size() << '\n';
}
int main()
{
bar( { 1,2,3 } ); // -> Initializer list of float
bar( { { 1,2,3 } } ); // -> Initializer list of initializer list of float
std::vector<float> v = { 1,2,3,4,5 };
bar( v ); // -> Vector of float
bar( { v } ); // -> Initializer list of vector of float
}
Live, here.
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