TL;DR
The problem is not specific/limited to std::vector
but instead is a consequence of the rule quoted below from the standard.
Let's see on case by case basis what is happening and why do we get the mentioned narrowing conversion error/warning when using lvalue
.
Case 1
Here we consider:
int lvalue = 6; // lvalue is not a constant expression
//---------------------------v------------------->constant expression so works fine
std::vector<int*> myvector { 6 };
std::vector<int*> myvector{ lvalue };
//--------------------------^^^^^^--------------->not a constant expression so doesn't work
First note that std::vector<int*>
does not have an initializer list constructor that takes an initializer list of int
.
So in this case the size_t count
ctor will be used. Now let's see the reason for getting narrowing conversion error/warning.
The reason we get an error/warning when using the variable named lvalue
while not when using a prvalue int
is because in the former case lvalue
is not a constant expression and so we have a narrowing conversion. This can be seen from dcl.init.list#7 which states:
A narrowing conversion is an implicit conversion
- from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.
(emphasis mine)
This means that the conversion from lvalue
(which is an lvalue expression) which is of type int
to size_t
parameter of the vector's std::vector::vector(size_t, /*other parameters*/)
ctor, is a narrowing conversion. But the conversion from prvalue int 6
to the size_t
parameter of the vector's std::vector::vector(size_t, /*other parameters*/)
is not a narrowing conversion.
To prove that this is indeed the case, lets look at some examples:
Example 1
int main()
{
//----------------v---->no warning as constant expression
std::size_t a{1};
int i = 1;
//----------------v---->warning here i is not a constant expression
std::size_t b{i};
constexpr int j = 1;
//----------------v---->no warning here as j is a constexpr expression
std::size_t c{j};
return 0;
}
Example 2
struct Custom
{
Custom(std::size_t)
{
}
};
int main()
{
//-----------v---->constant expression
Custom c{3}; //no warning/error here as there is no narrowing conversion
int i = 3; //not a constant expressoion
//-----------v---->not a constant expression and so we get warning/error
Custom d{i}; //warning here of narrowing conversion here
constexpr int j = 3; //constant expression
//-----------v------>no warning here as j is a constant expression and so there is no narrowing conversion
Custom e{j};
return 0;
}
Demo
Case 2
Here we consider:
//------------v-------------------------->note the int here instead of int* unlike case 1
std::vector<int> myvector{num_elements};//this uses constructor initializer list ctor
In this case there is a initializer list ctor available for std::vector<int>
and it will be preferred over the size_t count
constructor as we've used braces {}
here instead of parenthesis ()
. And so a vector of size 1
will be created. More details at Why is the std::initializer_list constructor preferred when using a braced initializer list?.
On the other hand, when we use:
std::vector<int> myvector(num_elements); //this uses size_t ctor
Here the size_t
ctor of std::vector
will be used as the initializer list ctor is not even viable in this case as we've used parenthesis ()
. And so a vector of size 6
will be created. You can confirm this using the example given below:
struct Custom
{
Custom(std::size_t)
{
std::cout<<"size t"<<std::endl;
}
Custom(std::initializer_list<int>)
{
std::cout<<"initializer_list ctor"<<std::endl;
}
};
int main()
{
Custom c(3); //uses size_t ctor, as the initializer_list ctor is not viable
return 0;
}
std::vector<int> myvector{num_elements};
creates a vector with one element. Which it does, becausestd::vector<int>
is not the same asstd::vector<int*>
. (It has a constructor that takes initializer lists of ints, and thestd::vector<int*>
does not.) – Coupe