Search code examples
c++constructorscopereturn

c++ : calling constructors via curly braces?


class A 
{
    int value_;
    public:
    A(int value):value_(value){}
};

A get_a1(int value)
{
    return A(value);
}

A get_a2(int value)
{
    return {value};
}


int main()
{
    A a1 = get_a1(1);
    A a2 = get_a2(2);
}

What is the difference between get_a1() and get_a2(), if any ?

How is return {value}; called ? (I guess "calling constructors via curly braces" is not the proper way to refers to this)


Solution

  • In your case, there is simply no difference. But if you modify your code a bit, there will be a visible difference!

    First of all, you can construct your type in different ways, all described here: initilization

    The difference come in, if your class provides also a constructor which takes a std::initializer_list.

    See the following code modified/extended to show the difference:

    class A 
    {   
        public:
            A(int value):value_(value){ std::cout << "int" << std::endl;}
            A(const std::initializer_list<int>& ){ std::cout << "list" << std::endl;}
            void print()
            {   
                std::cout << value_ << std::endl;
            }   
        private:
            int value_;
    };  
    
    A get_a1(int value)
    {   
        std::cout << "()" << std::endl;
        return A(value);
    }   
    
    A get_a2(int value)
    {
        std::cout << "{}" << std::endl;
        return {value};
    }
    
    
    int main()
    {   
        A a1 = get_a1(1);
        a1.print();
        A a2 = get_a2(2);
        a2.print();
    }   
    

    If you run that prog, you will see that using {} will call the constructor with std::initializer_list and using () will use your int constructor.

    Why is described here in the standard:

    §13.3.1.7 [over.match.list]/p1:

    When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

    • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
    • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

    If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

    In addition, initializer list constructors do not allow narrowing!

    §8.5.4 List-initialization

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