Preventing narrowing conversion when using std::initializer_list

做~自己de王妃 提交于 2020-01-01 03:59:07

问题


#include <iostream>

struct X {
    X(std::initializer_list<int> list) { std::cout << "list" << std::endl; }
    X(float f) { std::cout << "float" << std::endl; }
};

int main() {
    int x { 1.0f };
    X a(1);     // float (implicit conversion)
    X b{1};     // list
    X c(1.0f);  // float
    X d{1.0f};  // list (narrowing conversion) ARG!!!

    // warning: narrowing conversion of '1.0e+0f' from 'float' to 'int'
    // inside { } [-Wnarrowing]
}

Is there any other way of removing std::initializer_list from an overload list (i.e., making the non-list ctors more favorable) instead of using the ()-initialization, or at least prohibiting narrowing conversion to happen (apart from turning warning into error)?

I was using http://coliru.stacked-crooked.com/ compiler which uses GCC 4.8.


回答1:


Actually, a program containing a narrowing conversion in a brace list initializer is ill-formed. I am not sure why the compiler just gives you a warning, but it definitely should issue an error here (FWIW, Clang does that).

Also notice, that this is a narrowing (and therefore illegal) conversion as well:

int x { 1.0f }; // ERROR! Narrowing conversion required

Per paragraph 8.5.4/3 of the C++11 Standard:

List-initialization of an object or reference of type T is defined as follows:

— If T is an aggregate, aggregate initialization is performed (8.5.1). [...]

— Otherwise, if the initializer list has no elements [...]

— Otherwise, if T is a specialization of std::initializer_list<E>, [...]

— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed. [...]

To be more precise, the Standard only says that a "diagnostic" is required in this case, and a warning is a diagnostic, so the compiler's behavior is conforming - but I believe emitting an error would be a better behavior.




回答2:


That looks like a compiler error. You should be getting an error instead of a warning. Brace initialization should never implicitly narrow.

From the standard (§ 8.5.4)

struct B {
  B(std::initializer_list<int>);
};
B b1 { 1, 2 }; // creates initializer_list<int> and calls constructor
B b2 { 1, 2.0 }; // error: narrowing



回答3:


You can achieve what you want with std::enable_if.

#include <iostream>
#include <type_traits>

struct X {
    template<typename T, typename = typename std::enable_if<std::is_same<T,int>::value>::type>
    X(std::initializer_list<T>) { std::cout << "list" << std::endl; }
    X(float) { std::cout << "float" << std::endl; }
};

int main() {
    X a(1);     // float (implicit conversion)
    X b{1};     // list
    X c(1.0f);  // float
    X d{1.0f};  // float (yay)
}

Works on both g++4.8 and clang 3.2




回答4:


You can use -Wno-c++11-narrowing to turn off the errors:

Here's a sample test program:

#include <cstdint>

struct foo {
    int32_t a;
};

void foo(int64_t val) {
    struct foo A = { val };
}

Compile with clang++-3.8 with just -std=c++11, we get the stated error:

Add -Wno-c++11-narrowing, golden silence :-)

Of course, the narrowing issue might come back to bite you later, but it might occasionally be easier to delay the technical debt pain till later. ymmv :-)



来源:https://stackoverflow.com/questions/16939471/preventing-narrowing-conversion-when-using-stdinitializer-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!