Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ std::vector initializer_list overload ambiguity (g++/clang++)

Consider the following code:

#include <vector>

#define BROKEN

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

class Class {
public:
    Class(std::vector<Var> arg) : v{arg} {}

    std::vector<Var> v;
};

Clang++ (7.0.1) compiles this without errors whether or not BROKEN is defined, but g++ (8.2.1) raises an error if BROKEN is defined:

main.cpp:9:20: error: cannot convert ‘std::vector<Var>’ to ‘int’ in initialization
  Var(T x) : value(x) {}
                    ^

As far as I know, the uniform intialization used here should select the std::vector(std::vector&&) constructor in both cases; apparently, however, g++ instead sees the {arg} as an initializer list and tries to initialize the first element of v with Var applied to a vector, which won't work.

If BROKEN is not defined, g++ is apparently smart enough to realize that the initializer_list overload isn't going to work.

Which is the correct behavior, or are both allowed by the standard?

BROKEN defined gcc
BROKEN not defined gcc
BROKEN defined clang

like image 899
tomsmeding Avatar asked Mar 11 '19 10:03

tomsmeding


1 Answers

Here is are two ways to fix that issue:

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x, typename std::enable_if<std::is_convertible<T, int>::value>::type* = nullptr) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

Live demo.

Or:

class Class {
public:
    Class(std::vector<Var> arg) : v(arg) {}

    std::vector<Var> v;
};

Live demo.

Now apparently in case of gcc tries use template parameter as initialization list. When braces are used and initialization list is defined, initialization list has higher priority then copy constructor.

So adding proper enable_if solves the problem since it protects from creating initialization list version of constructor. In C++20 concepts will solve this in better way.

And when parenthesis instead braces are used to initialize v initialization list is not preferable.

I'm not sure who is right (IMO clang, but this is just a gut feeling). Maybe someone who knows standard better can tell.

like image 163
Marek R Avatar answered Oct 22 '22 08:10

Marek R