为什么仅当我用大括号初始化一个对象时才需要从 int 到 float 的缩小转换?

Why is a narrowing conversion from int to float only needed if I brace-initialise an object?

我运行进入我认为很奇怪的事情:

#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 初始化它,就像在另一个例子中一样,这个编译很好。

在此声明中

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 A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list or reference to possibly cv-qualified std::initializer_list for some type E, and either there are no other parameters or else all other parameters have default arguments (8.3.6). [ Note: Initializer-list constructors are favored over other constructors in list-initialization

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

1 When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.

(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the lements of the initializer list.

这是一个演示程序

#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 List-initialization)

7 A narrowing conversion is an implicit conversion

(7.3) — from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or

所以在此列表初始化

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

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

例如如果在上面的演示程序中你会写

size_t n = 1;

A a1{ n, 1.0f };

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

来自 cppreference on list initialisation.

list-initialization limits the allowed implicit conversions by prohibiting the following:

  • ...

  • conversion from an integer type to a floating-point type, except where the source is a constant expression whose value can be stored exactly in the target type

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

的构造函数