Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a vector or a vector of vectors or a vector of ... (you get the idea) to a function

Tags:

c++

vector

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?

like image 385
xonxt Avatar asked Dec 23 '22 19:12

xonxt


2 Answers

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.

like image 150
NathanOliver Avatar answered Jan 12 '23 01:01

NathanOliver


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.

like image 44
Bob__ Avatar answered Jan 12 '23 01:01

Bob__