Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ initializer list overload disambiguation

I have a question about C++ initializer list disambiguation which exhibits different behaviors between gcc, clang and Visual Studio.

I wonder if this is "undefined behavior" (incorrect program) or if one of these compilers has a bug. Any idea?

Consider the following declarations:

class Arg
{
public:
    Arg(int i);
};

class Object
{
public:
    Object(const char* str, int i);
    Object(const char* str, const std::initializer_list<Arg>& args);
};

And now this usage:

Object c("c", {4});

Which constructor should be used? The one with int (assuming that the braces around the literal are superfluous) or the one with the initializer list (with implicit conversion from int to Arg).

GCC 10.2.0 chooses the constructor with the initializer list of Arg.

Clang 11.2.2-2 chooses the constructor with int and reports a warning about the braces:

initlist.cpp:46:19: warning: braces around scalar initializer [-Wbraced-scalar-init]
    Object c("c", {4});
                  ^~~

Visual Studio 2019 16.8.6 chooses the constructor with int without warning (/W4).

On a majority standpoint, the constructor with int wins. On the other hand, if we directly use std::initializer_list<int> instead of std::initializer_list<Arg> (no implicit call to Arg constructor), all three compilers choose the constructor with the initializer list.

Because of the ambiguity and the difference of behavior, this kind of code should be avoided anyway. But I am curious to understand who is wrong? The undefined application code or one of the compilers?

Full source code below, in case anyone wants to try:

#include <iostream>

class Arg
{
public:
    int value;
    Arg(int i);
};

class Object
{
public:
    Object(const char* str, int i);
    Object(const char* str, const std::initializer_list<Arg>& args);
};


Arg::Arg(int i) : value(i)
{
    std::cout << "Arg(" << i << ")" << std::endl;
}

Object::Object(const char* str, int i)
{
    std::cout << "Object(\"" << str << "\", " << i << ")" << std::endl;
}

Object::Object(const char* str, const std::initializer_list<Arg>& args)
{
    std::cout << "Object(\"" << str << "\", {";
    bool comma = false;
    for (auto it = args.begin(); it != args.end(); ++it) {
        if (comma) {
            std::cout << ", ";
        }
        comma = true;
        std::cout << it->value;
    }
    std::cout << "})" << std::endl;
}

int main(int argc, char* argv[])
{
    Object a("a", 1);
    Object b("b", {2, 3});
    Object c("c", {4});
}

With GCC:

Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Arg(4)
Object("c", {4})

With clang and VS:

Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Object("c", 4)
like image 344
Thierry Lelegard Avatar asked Feb 25 '21 16:02

Thierry Lelegard


People also ask

What is initializer_list in C++?

(not to be confused with member initializer list ) An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T.

What is the purpose of the member initializer list?

Before the compound statement that forms the function body of the constructor begins executing, initialization of all direct bases, virtual bases, and non-static data members is finished. Member initializer list is the place where non-default initialization of these objects can be specified.

How to initialize class member variables in C++?

To solve this problem, C++ provides a method for initializing class member variables (rather than assigning values to them after they are created) via a member initializer list (often called a “member initialization list”). Do not confuse these with the similarly named initializer list that we can use to assign values to arrays.

How to initialize a reference member of a class?

Reference members must be initialized using Initializer List. In the following example, “t” is a reference member of Test class and is initialized using Initializer List. In the following example, an object “a” of class “A” is data member of class “B”, and “A” doesn’t have default constructor. Initializer List must be used to initialize “a”.


1 Answers

{4} to const std::initializer_list<Arg>& is a user-defined conversion.

{4} to int is a standard conversion.

The latter wins. This is a GCC bug.

List-initialization to an initializer_list beats others when the conversion sequences are of the same form. They are not here.

like image 192
T.C. Avatar answered Oct 27 '22 01:10

T.C.