Konstantin Vinogradov
Konstantin Vinogradov

Reputation: 29

C++ default brace-enclosed initializer list

Let's say I have c_student class with several members and no constructors:

#include <iostream>
#include <string>
#include <initializer_list>
#include <typeinfo>

class c_student{
    // private:
    public:
        std::string name = "John";
        int         mark = 5;
        std::string second_name="Doe";

        void print(){
            std::cout << name <<" " << second_name << " : "<< mark<<"\n";
        }
    };

int main(){
    c_student stud_c  = {"Luke ",420};
    stud_c.print();
    c_student stud_d  = {"Mark "};
    stud_d.print();
        
}

This code works fine, but let's say I want to define custom constructors inside the class:

        c_student (std::string n):name(n){};
        c_student() = default;

If I add them to class, the compiler complains that:

47_object_initialization.cpp:32:34: error: could not convert ‘{"Luke ", 420}’ from ‘<brace-enclosed initializer list>’ to ‘c_student’
   32 |  c_student stud_c  = {"Luke ",420};
      |                                  ^
      |                                  |
      |                                  <brace-enclosed initializer list>

I want to keep using default constructor for {} and so need to write something like:

c_student( std::initializer_list <???>) = default; << Pseudo-code only!

But can't get how exactly. Could anyone points my attention on the right page of CPP reference?

Upvotes: 2

Views: 1710

Answers (3)

Adrian Mole
Adrian Mole

Reputation: 51884

You can add another user-defined constructor to handle the initializer list with two entries (and keep adding such constructors for each possible list size and order). The following compiles and works as expected:

#include <iostream>
#include <string>

class c_student {
private:
    std::string name = "John";
    int         mark = 5;
    std::string second_name = "Doe";
public:
    void print() {
        std::cout << name << " " << second_name << " : " << mark << "\n";
    }
    c_student(std::string n) : name(n) { }  // Called for initializer list {"xxx"}
    c_student(std::string n, int m) : name(n), mark(m) { } // Called for list {"xxx", m}
    c_student(std::string n, std::string s, int m) : name(n), mark(m), second_name(s) { } // Called for list {"xxx", "yyy", m}
    c_student() = default;
};

int main()
{
    c_student stud_c = { "Luke", 420 };
    stud_c.print();
    c_student stud_d = { "Mark" };
    stud_d.print();
    c_student stud_e = { "John", "Smith", 333 };
    stud_e.print();
    return 0;
}

Upvotes: 1

cigien
cigien

Reputation: 60278

You can use brace-initialization to achieve what you want by replacing all the constructors with the following one:

c_student (std::string name = "John", int mark = 5, std::string second_name = "Doe") 
  : name(name), mark(mark), second_name(second_name) {};

Also, you can remove the default member initializers since the only way to construct a c_student will give them the default values if they are not provided.

Here's a demo.

Upvotes: 2

Cory Kramer
Cory Kramer

Reputation: 118001

This is unfortunately not possible. The reason your code originally worked is due to Aggregate initialization. As soon as you created a user-defined constructor, your class is no longer an aggregate type

An aggregate is one of the following types:

  • class type that has
    -- no user-declared constructors

Upvotes: 2

Related Questions