为什么我不能用 l 值初始化这个 std::vector?



我遇到了一个有趣的问题,我不明白发生了什么:

/* I WANT 6 ELEMENTS */
int lvalue = 6;
std::vector<int*> myvector { 6 }; /* WORKS FINE */
std::vector<int*> myvector{ lvalue }; /* DOESN'T WORK */
/* Element '1': conversion from 'int' to 'const unsigned __int64 requires a narrowing conversion */

从我所看到的,我所提供的单个整数参数可以被解释为调用带参数size_type count的构造函数,或者接受初始化列表的构造函数。它似乎只在我提供一个l值时调用initialiser_list构造函数,但当我给出一个r值int时调用size_t count构造函数(好吧,至少是字面意思)。为什么会这样?

这也意味着:

int num_elements = 6;
std::vector<int> myvector{num_elements};

得到大小为1的vector;

std::vector<int> myvector(num_elements);

的结果是一个大小为num_elements的向量,但我认为这个初始化应该避免,因为偶尔会遇到最令人烦恼的解析问题。

TL;DR

这个问题不是特定的/限于std::vector,而是下面引用的标准规则的结果。


让我们逐个看看发生了什么,为什么在使用lvalue时我们会得到提到的缩小转换错误/警告。

案例1

这里我们考虑:

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 

首先注意std::vector<int*>没有一个初始化列表构造函数来接受int的初始化列表。

所以在这种情况下,size_t count将被使用。现在让我们看看出现窄化转换错误/警告的原因。

当使用名为lvalue的变量而不是使用右值int时,我们得到错误/警告的原因是,在前一种情况下,lvalue不是常量表达式,因此我们有一个窄化转换。这可以从dcl.init中看到。列表#7:

窄化转换是隐式转换

  • 从整数类型或无作用域枚举类型转换为不能表示原始类型的所有值的整数类型,除非源是常量表达式积分提升后,其值将符合目标类型。

(强调我的)

这意味着从int类型的lvalue(左值表达式)到vector的std::vector::vector(size_t, /*other parameters*/)ctor的size_t参数的转换是窄化转换. 但是从整型右值6到vector的std::vector::vector(size_t, /*other parameters*/)形参size_t的转换是,而不是窄化转换.

为了证明这确实是事实,让我们看一些例子:

示例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;
}
示例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;
}

演示

案例2

这里我们考虑:

//------------v-------------------------->note the int here instead of int* unlike case 1 
std::vector<int> myvector{num_elements};//this uses constructor initializer list ctor 

在这种情况下,std::vector<int>有一个初始化列表变量,它将是首选size_t count构造函数上,我们用大括号{}代替括号()。因此,将创建一个大小为1的矢量。更多详细信息请参见为什么在使用带括号的初始化列表时首选std::initializer_list构造函数?


另一方面,当我们使用:

std::vector<int> myvector(num_elements); //this uses size_t ctor

这里std::vectorsize_t将被用作初始化列表的ctor,在这种情况下甚至是不可行的,因为我们已经使用了括号()。因此,将创建一个大小为6的矢量。您可以使用下面给出的示例来确认这一点:

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; 
}

最新更新