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

Why can't I initialize this std::vector with an l-value?

提问人:Zebrafish 提问时间:6/5/2022 最后编辑:user12002570Zebrafish 更新时间:8/26/2022 访问量:1664

问:

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

/* 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 */

据我所知,我提供的单个整数参数可以解释为调用带有参数的构造函数,也可以解释为采用初始值设定项列表的构造函数。它似乎只在我提供 l 值时才调用构造函数,但当我提供 r 值时才调用构造函数(好吧,至少是文字)。为什么会这样?size_type countinitialiser_listsize_t countint

这也意味着:

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

生成仅大小的向量1;

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

导致 大小 的向量,但我认为应该避免这种初始化,因为偶尔会遇到最棘手的解析问题。num_elements

C++ 语言律师 缩小范围

评论

0赞 Zebrafish 6/5/2022
@AnoopRana我刚刚检查了我的编译器 Visual Studio 2022,它生成了一个大小为 1 的向量。
0赞 Nathan Pierson 6/5/2022
@AnoopRana 您的演示与提问者的代码不匹配,它会添加一组额外的大括号。似乎有必要对语法如何影响重载解决进行一些解释。
1赞 Nathan Pierson 6/5/2022
啊。看起来我们也在结合一些不同的东西。该声明是创建一个具有一个元素的向量。它确实如此,因为与 .(它有一个构造函数,它接受 int 的初始值设定项列表,而 则不这样做。std::vector<int> myvector{num_elements};std::vector<int>std::vector<int*>std::vector<int*>
1赞 David C. Rankin 6/5/2022
构造函数 10 解释了 std::vector::vector 的区别,并参见示例上方的注释,专门解决此问题。
0赞 JHBonarius 6/5/2022
这是关于“新”括号初始化的常见抱怨(尤其是必须向初学者教授这一点的教师和培训师)。他们甚至改变了语言版本之间的行为(一个重大的变化),因为它太令人困惑了。委员会内部对此进行了大量辩论。但它仍然令人困惑。这是C++出错默认值的原因之一。

答:

24赞 user12002570 6/5/2022 #1

TL的;博士

该问题不是特定/限于的,而是下面从标准中引用的规则的结果。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

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

当使用名为 while not 的变量时,我们收到错误/警告的原因是,在前一种情况下,它不是常量表达式,因此我们有一个缩小的转换。这可以从 dcl.init.list#7 中看出,它指出:lvalueintlvalue

缩小转换范围是隐式转换

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

(强调我的)

这意味着从向量 ctor 的类型为参数的转换(左值表达式)是缩小转换。但是从 prvalue int 到向量参数的转换并不是缩小转换lvalueintsize_tstd::vector::vector(size_t, /*other parameters*/)6size_tstd::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;
}

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

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


另一方面,当我们使用:

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

这里 的 ctor 将用作初始值设定项列表 ctor 在这种情况下甚至不可行,因为我们使用了括号。因此,将创建一个大小的向量。您可以使用下面给出的示例来确认这一点:size_tstd::vector()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; 
}