I have a program that simulates a window; So I have the content of the window stored in a member data content which is a std::string type:

class Window {
    using type_ui = unsigned int;
    public:
        Window() = default;
        Window(type_ui, type_ui, char);
        void print()const;

    private:
        type_ui width_{};
        type_ui height_{};
        char fill_{};
        std::string content_{};
        mutable type_ui time_{};
};

Window::Window(type_ui width, type_ui height, char fill) :
    width_{ width }, height_{ height }, fill_{ fill },
    content_{ width * height, fill } { // compile-time error here?
    //content( width * height, fill ) // works ok
}

void Window::print()const {
    while (1) {
        time_++;
        for (type_ui i{}; i != width_; ++i) {
            for (type_ui j{}; j != height_; ++j)
                std::cout << fill_;
            std::cout << std::endl;
        }
        _sleep(1000);
        std::system("cls");
        if (time_ > 10)
            return;
    }
}


int main(int argc, char* argv[]) {

    Window main{ 15, 25, '*' };
    main.print();

    std::string str{5u, '*'}; // compiles but not OK
    std::string str2(5u, '*'); // compiles and OK
    cout << str << endl; // ♣* (not intended)
    cout << str2 << endl; // ***** (ok)

    std::cout << std::endl;
}

AS you can see above I couldn't initialize the member content with curly-braces-initializer-list which the compiler complains about "narrowing type". But it works with "Direct initialization".

  • Why I cannot use Curly-brace-initialization-list above in the Constructor-initializer-list to invoke std::string(size_t count, char).

  • Why this std::string str{5u, '*'}; // compiles but not OK Works but gives not intended ouptu?

  • The thing that matters me a lot is why the same initialization doesn't work on constructor-member-initialization-list but works in main (with not intended result)?

1 Answers

8
Raindrop7 On Best Solutions
  • First, because the std::string's constructor sts::string(size_t count, char) is explicit thus you cannot invoke it implicitly.

  • Second, You are not invoking std::string(size_t, char) in content{width * height, fill} but in fact you are invoking std::string(std::initializer_list<char>). Thus the expression width * height yields an unsigned int then implicitly converted to char which is "related type" thus for exmple you passed Window main{ 15, 25, '*' }; Which yields (char)15 * 25 = (char)375 which is Undefined Behavior because this value overflows a signed char. You may get on your machine "♣" or other values as the first element in the initializer-list But it is undefined behavior and "" as the second element in the initializer-list. So as a result you are passing std::initializer_list{'♣', ''}.

    • Use direct initialization as long as you are invoking an explicit constructor that has more than one parameter.
  • The answer for the second question: "The thing that matters me a lot is why the same initialization doesn't work on constructor-member-initialization-list but works in main (with not intended result)?":

In fact it doesn't have any relation with "Constructor-member-initializer-list" or not but in fact consider this:

    char c{ 127 }; // the maximum integer positive value that a signed char can hold so no narrowing conversion here. So it is OK.

    char c2{ 128 }; // Now 128 overflows the variavle c2. c2 is of type char and as you know it can hold positive values in range 0 to 127 and negative -1 to -128
    unsigned char uc{ 255 }; // ok because an unsigned char can hold 255
    unsigned char uc2{ 300 }; // error as the error above. An unsigned char can hold 255 as max positive value.

    unsigned char uc3 = 321; // ok // ok but you may get a warning. a Copy-initialization is not safe.
    cout << uc3 << endl; // you may get `A`. 321 % 256 = 65. 65 % 256 = 65. 65 in ASCII in some implementations it represents the character "A".

Although it is not good thing to overflow uc3 above but the result is well-defined. (overflowing an unsigned Xtype).

  • But look at this:

    char c3 = 321; // signed char overflows
    cout << c3 << endl; // you may get "A" and think it is correct.
    

Above it is Undefined Behavior. Never try to overflow signed types.

constexpr int i = 10;
constexpr int j = 5;
std::string str{ i * j, 'f' }; // ok as long as the values are constexpr and don't cause any narrowing conversion this value: 10 * 5 = 50 which is a well defined signed-char value.

int i = 10;
int j = 5;
std::string str{ i * j, 'f' }; // error. the values of i and j are not constant thus it may overflow a signed char causing narrowing conversion thus the compiler is wise enough to prevent this initialization.