了解何时通过 class 构造函数在 () 和 {} 初始化中进行检查

Understanding when checks happen in () and {} initization through class constructors

据我了解,{} 是一种初始化变量的方法,与其他方法相比具有一些“安全”优势,例如禁止缩小:

int some_int_a = 1.2;  // narrows
int some_int_b (1.2);  // narrows
int some_int_c {1.2};  // does NOT compile, cannot narrow

到目前为止一切顺利。我最近发现但我不完全理解的是,这种检查何时发生?例如,在下面的代码中:

#include <iostream>

class ExClass{
    private:
        const int i;
    public:
        ExClass(int i=0): i{i} {
            // needed even if empty
        }
        void print(void){
            std::cout << "const int i = " << i << std::endl;
        }
};

int main(void)
{

    ExClass ex_a (2.3);  // narrows! this was surprising to me, I (probably naively)
                         // expected i{i} in the constructor to forbid this.
    ex_a.print();

    ExClass ex_b {2.3};  // does not compile
    ex_b.print();

    return 0;
}

我想这意味着在 ex_a 的情况下,首先使用收缩转换完全创建中间 int,然后这个中间 int 用于括号初始化i 在构造函数中。而在第二种情况下,中间 int 不能用有冲突的输入进行括号初始化,对吗?

有没有办法以没有“中间”缩小的方式编写内容,以便 class 括号初始化检测到错误输入?

调试器可能会很快向您显示! 看看你在做什么: if(ExClass(next) == false || opening_brackets_stack.empty() == false)

当您尝试将它与右括号进行比较时,ExClass 的类型是什么

提示:想一想何时创建 Bracket 的每个新实例。都是括号吗? 都是开括号吗?

这里发生的是构造函数:

ExClass(int i=0): i{i}

采用 int 参数,并在此处缩小:

ExClass ex_a (2.3);

很好。一旦进入构造函数,i 就是一个 int(参数,而不是成员)。因此 i{i} 没有变窄(它是 intint)。当您将构造函数更改为:

时,您会得到 the error
ExClass(double i=0): i{i} { }

因为现在 i{i} 确实在变窄。

请注意,gcc 仅使用默认设置发出 a warning,需要 -pedantic-errors 才能将其识别为错误。感谢 Remy 指出它实际上是一个错误,而不是警告。

大括号初始化 (C++11 起) 防止变窄。正如 cppreference 缩小转化率 中所述:

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

  • conversion from a floating-point type to an integer type
  • conversion from a long double to double or to float and conversion from double to float, except where the source is a constant expression and overflow does not occur
  • 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
  • conversion from integer or unscoped enumeration type to integer type that cannot represent all values of the original, except where source is a constant expression whose value can be stored exactly in the target type
  • conversion from a pointer type or pointer-to-member type to bool (since C++20)

因此,比起替代方案,更喜欢大括号初始化。

Is there a way to write things in such a way that there is no "intermediate" narrowing, so that the class bracket initialization detects the faulty input?

你可以让构造函数接受一个模板参数,这样它就接受调用者传入的任何类型,例如:

#include <iostream>

class ExClass{
    private:
        const int i;
    public:
        template<typename T>
        ExClass(T i=T{}): i{i} {
            // needed even if empty
        }
        void print(void){
            std::cout << "const int i = " << i << std::endl;
        }
};

int main(void)
{

    ExClass ex_a (2.3);  // should not compile!
    ex_a.print();

    ExClass ex_b {2.3};  // should not compile!
    ex_b.print();

    return 0;
}

不幸的是,GCC 默认允许缩小范围,发出 警告 而不是错误(使用 -pedantic-errors 强制错误):

warning: narrowing conversion of 'i' from 'double' to 'int' [-Wnarrowing]

Clang 发出错误,但是:

error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]