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

Zeb*_*ish 26 c++ language-lawyer narrowing

我遇到了一个有趣的问题,我无法理解发生了什么:

/* 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 */
Run Code Online (Sandbox Code Playgroud)

据我所知,我提供的单个整数参数可以解释为使用 argument 调用构造函数size_type count,也可以解释为采用初始值设定项列表的构造函数。似乎initialiser_list只有当我提供左值时才调用构造函数,而size_t count当我给出右值int(好吧,至少是一个文字)时才调用构造函数。为什么是这样?

这也意味着:

int num_elements = 6;
std::vector<int> myvector{num_elements};
Run Code Online (Sandbox Code Playgroud)

结果仅是大小为 的向量1

std::vector<int> myvector(num_elements);
Run Code Online (Sandbox Code Playgroud)

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

use*_*570 24

长话短说

该问题并不特定/限于,而是以下标准引用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 
Run Code Online (Sandbox Code Playgroud)

首先请注意,它std::vector<int*>没有采用 的初始值设定项列表的初始值设定项列表构造函数int

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

我们在使用名为 while 的变量时收到错误/警告lvalue而不是在使用纯右值时收到错误/警告的原因int是因为在前一种情况下lvalue 不是常量表达式,因此我们进行了缩小转换。这可以从dcl.init.list#7中看出,其中指出:

缩小转换是隐式转换

  • 从整数类型或无作用域枚举类型到无法表示原始类型的所有值的整数类型,除非源是常量表达式,其值在整型提升后将适合目标类型。

(强调我的)

这意味着从lvalue类型(左值表达式)intsize_t向量构造函数的参数的转换std::vector::vector(size_t, /*other parameters*/)收缩转换6但从 int 纯右值到size_t向量参数的转换std::vector::vector(size_t, /*other parameters*/)不是缩小转换

为了证明情况确实如此,让我们看一些例子:

实施例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;
}
Run Code Online (Sandbox Code Playgroud)

实施例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;
}
Run Code Online (Sandbox Code Playgroud)

演示


案例2

这里我们考虑:

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

在这种情况下,有一个可用的初始值设定项列表构造函数,std::vector<int>并且它将优先size_t count构造函数,因为我们{}在这里使用了大括号而不是圆括号()1因此将创建一个大小向量。更多详细信息,请参阅为什么在使用花括号初始化器列表时首选 std::initializer_list 构造函数?


另一方面,当我们使用:

std::vector<int> myvector(num_elements); //this uses size_t ctor
Run Code Online (Sandbox Code Playgroud)

这里的size_tctorstd::vector将被用作初始化列表 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; 
}
Run Code Online (Sandbox Code Playgroud)