TransWikia.com

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

Stack Overflow Asked by Zorglub29 on February 23, 2021

From what I understand, {} is a way to initialize variables with some "safety" advantages over other methods, such as forbidding narrowing:

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

So far so good. The thing I recently discovered, that I do not understood fully, is when does this sort of check happen? For example, in the following code:

#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;
}

I suppose this means that in the case of ex_a, first an intermediate int is created fully with a narrowing conversion, then this intermediate int is used to bracket-initialize i in the constructor. While in the second case, the intermediate int cannot be bracket-initialized with a conflicting input, is that right?

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?

4 Answers

What happens here is that the constructor:

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

Takes an int parameter, and narrowing here:

ExClass ex_a (2.3);

is fine. Once you are in the contructor, i is an int (the parameter, not the member). Hence there is no narrowing in i{i} (it is int to int). You do get the error when you change your constructor to:

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

Because now there is actually narrowing in i{i}.

Note that gcc only issues a warning with default settings and needs -pedantic-errors to recognize it as error. Thanks to Remy for pointing out that it actually is an error, not a warning.

Correct answer by largest_prime_is_463035818 on February 23, 2021

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?

You can make the constructor take a template parameter so it takes on the type of whatever the caller passes in, eg:

#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;
}

Unfortunately, GCC allows this narrowing by default, issuing a warning rather than an error (use -pedantic-errors to force an error):

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

Clang issues an error, though:

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

Answered by Remy Lebeau on February 23, 2021

Brace initialization (since C++11) prevents narrowing. As cppreference states under Narrowing conversions:

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)

So prefer brace initialization over the alternatives.

Answered by jignatius on February 23, 2021

The debugger would probably show you this very quickly! Look at what you are doing: if(ExClass(next) == false || opening_brackets_stack.empty() == false)

What is the type of ExClass when you try to compare it with a close bracket

Hint: think about when you create each new instance of a Bracket. Are they all brackets? Are they all open brackets?

Answered by Drian La on February 23, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP