If we stop using vector so we can see what constructors are getting used, then maybe it gets more clear what's going on.
template <typename T>
struct V {
V() { std::cout << "V Default\n"; }
V(const V&) { std::cout << "V Copy\n"; }
V(V&&) { std::cout << "V Move\n"; }
V(std::initializer_list<T>) { std::cout << "V list<" << typeid(T).name() << ">\n"; }
};
struct Foo {
template <class T>
Foo(T) { std::cout << "Foo <" << typeid(T).name() << ">\n"; }
Foo(const Foo&) = delete;
Foo(Foo&&) { std::cout << "Foo Move\n"; }
};
https://godbolt.org/z/9qs9bvjjz
Now let's try to copy construct a V<Foo> from a V<Foo>, using both () and {}.
V<Foo> v0{};
V<Foo> v1(v0);
V<Foo> v2{v1};
To create v1 with both gcc and clang, we get "V Copy", copy ctor of V chosen as best. For v2 on clang we get "V Copy" as well, but for gcc, we get "V list<Foo>". It's using the initializer list constructor of V instead of the copy constructor.
Now, one should wonder, how did we get a std::initializer_list<Foo> from a list of one element of V<Foo>? That's because of the template constructor of Foo, which provides a conversion constructor to allow creating a Foo from a V<Foo>.
gcc acts the same as if we had written:
V<Foo> v2( { Foo(v1) } );
I think gcc is correct here.
V<Foo> v2{v1} is direct-list-initialization and the resolution is done in two phases. The first is to consider only std::initializer_list constructors, then the second is to consider all constructors with the list as the arguments. The first phase should produce a match, by converting v1 into a Foo, and that's what gcc picks.
In case it's not clear, once the initializer_list constructor is chosen, you get an error with vector because it's not possible to use that constructor with a list of non-copyable objects. The initializer_list's elements are const and the vector is copy-initialized from it.