【问题标题】:Why is a narrowing conversion from int to float only needed if I brace-initialise an object?为什么只有在我大括号初始化对象时才需要从 int 到 float 的缩小转换?
【发布时间】:2021-11-05 04:48:44
【问题描述】:

我遇到了我认为很奇怪的事情:

#include <vector>

int numqueues = 1;
std::vector<float> priorities{numqueues, 1.f };
//^^^ warning: narrowing conversion of numqueues from int to float

//std::vector<float> priorities(numqueues, 1.f );
//^^^ No warning or error. And it's not because it's parsed as a function declaration
// as I can call push_back in main.

int main()
{
    priorities.push_back(1);// No narrowing conversion needed
}

我已经用几个编译器试过了,这不会编译。

编辑:据说 initializer_list 优先,看起来确实如此,但我试图模仿 std::vector 并且在此示例中我没有得到缩小转换错误:

#include <vector>
#include <iostream>
#include <initializer_list>

template <typename T>
class MyVector
{public:
    MyVector(size_t s, float f) {
        std::cout << "Called constructor\n";
    }
    MyVector(std::initializer_list<T> init)
    {
        std::cout << "Called initializer list constructor\n";
    }

};

int main()
{

    MyVector<float> foo{ size_t(3), 2.f };
}

我做了同样的事情,用 size_t 和 float 初始化它,就像在另一个例子中一样,这个编译得很好。

【问题讨论】:

  • 您在测试中使用了一个常量来表示大小,而不是变量。这是您的诊断信息wandbox.org/permlink/ECXJE0UmSeOl1Zk5
  • @StoryTeller-UnslanderMonica 规则对此有何规定?常量不需要缩小转换?
  • 当它是一个常量表达式时,编译器能够知道转换中是否存在重大损失。所以它不必只对类型发出警报。
  • @StoryTeller-UnslanderMonica 那么,如果我真的想调用 (size_t, float) 的构造函数并且因为最麻烦的解析而不能调用,会发生什么?好吧,从这个意义上说,它可能会被误认为是函数声明,这一点并不含糊,但我见过一些情况,当我认为它不可能是函数声明时,它被推断为函数声明。
  • 最麻烦的解析总是可以避免的,只要多加一些括号就可以了。丑陋,但可以避免。

标签: c++ class constructor initializer-list type-narrowing


【解决方案1】:

在此声明中

std::vector<float> priorities{numqueues, 1.f };

编译器使用初始化列表构造函数。

vector(initializer_list<T>, const Allocator& = Allocator());

禁止初始化列表的窄化转换。

在此声明中

std::vector<float> priorities(numqueues, 1.f );

编译器使用指定元素数量及其初始值设定项的构造函数。

vector(size_type n, const T& value, const Allocator& = Allocator());

来自 C++ 14 标准(8.5.4 列表初始化)

2 一个构造函数是一个初始化列表构造函数,如果它的第一个 参数的类型为 std::initializer_list 或引用可能 某些类型 E 的 cv 限定 std::initializer_list ,或者 没有其他参数,否则所有其他参数都有 默认参数(8.3.6)。 [ 注意:Initializer-list 构造函数是 在列表初始化中优于其他构造函数

和(13.3.1.7 通过列表初始化进行初始化)

1 当非聚合类类型 T 的对象被列表初始化时 8.5.4 规定重载决议是根据 根据本节中的规则,重载决议选择 构造函数分两个阶段:

(1.1) — 最初,候选函数是初始化列表 类 T 的构造函数(8.5.4)和参数列表由 初始化器列表作为单个参数。

(1.2) — 如果没有找到可行的初始化列表构造函数,则重载 再次执行解析,其中候选函数都是 类 T 的构造函数和参数列表由 初始化列表的元素。

这是一个演示程序

#include <iostream>
#include <initializer_list>

struct A
{
    A( std::initializer_list<float> )
    {
        std::cout << "A( std::initializer_list<float> )\n";
    }
    
    A( size_t, float )
    {
        std::cout << "A( size_t, float )\n";
    }
};

int main() 
{
    A a1 { 1, 1.0f };
    A a2( 1, 1.0f );
    
    return 0;
}

程序输出是

A( std::initializer_list<float> )
A( size_t, float )

至于你的附加问题(8.5.4 列表初始化)

7 窄化转换是隐式转换

(7.3) — 从整数类型或无作用域枚举类型到 浮点类型,除非源是常量 表达式和转换后的实际值将适合 目标类型,并在转换回时生成原始值 原始类型,或

所以在这个列表中初始化

MyVector<float> foo{ size_t(3), 2.f };

使用适合浮点类型的常量表达式size_t( 3 )

例如,如果在上面的演示程序中你将编写

size_t n = 1;

A a1{ n, 1.0f };

那么编译器应该会发出关于缩小转换的消息(至少MS VS 2019 C++编译器会发出这样的错误消息)。

【讨论】:

  • 它有一个构造函数,它接受一个 size_t 和一个浮点数,为什么不调用构造函数?我使用大括号初始化而不是 () 来避免最麻烦的解析。
  • @Zebrafish 初始化列表构造函数具有优先级。
  • @Zebrafish 另请参阅我的附加答案。
【解决方案2】:

来自cppreference on list initialisation

list-initialization 通过以下方式限制允许的隐式转换 禁止:

  • ...

  • 从整数类型到浮点类型的转换,除非源是可以存储其值的常量表达式 完全符合目标类型

通常,列表初始化不会为您进行隐式转换。另外,因为std::vector 有一个构造函数vector( std::initializer_list&lt;T&gt; init, const Allocator&amp; alloc = Allocator() );,它是被调用来构造vector 的构造函数。

【讨论】:

  • std::vector 有一个构造函数,在示例的情况下需要一个 size_t 和一个浮点数,为什么不调用构造函数?
  • 它在调用构造函数,它只是使用列表的元素,它不允许缩小转换。
  • 有一个构造函数vector(size_type count, const T& value, const Allocator& alloc = Allocator());它如何用初始化列表调用它?有一个构造函数需要一个 std::initializer_list,但我调用了我在那里放了一个 int 和一个浮点数,它不应该调用 size_t、float 构造函数吗?
  • 我相信它没有,它用vector( std::initializer_list&lt;T&gt; init, const Allocator&amp; alloc = Allocator() );调用构造函数
猜你喜欢
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 2013-07-05
  • 1970-01-01
  • 2021-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多