Bug 84849 - [DR1228] Ambiguous resolution of braze initializer list to a class with explicit constructors
Summary: [DR1228] Ambiguous resolution of braze initializer list to a class with expli...
Status: SUSPENDED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 7.2.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: rejects-valid
Depends on:
Blocks:
 
Reported: 2018-03-13 16:05 UTC by Jose Dapena Paz
Modified: 2024-07-15 15:33 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2022-04-28 00:00:00


Attachments
Test case (244 bytes, text/x-csrc)
2018-03-13 16:05 UTC, Jose Dapena Paz
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Jose Dapena Paz 2018-03-13 16:05:34 UTC
Created attachment 43644 [details]
Test case

For a template class with explicit copy and move constructors, passing a std::vector of std::string using initializer list syntax fails because of geing ambiguous.

Attached test example.

main.cc: In function ‘int main(int, char**)’:
main.cc:22:62: error: call of overloaded ‘Foo(<brace-enclosed initializer list>)’ is ambiguous
   static Foo<std::vector<std::string>> vector({"a", "b", "c"});
                                                              ^
main.cc:14:3: note: candidate: Foo<T>::Foo(const Foo<T>&) [with T = std::vector<std::__cxx11::basic_string<char> >] <deleted>
   Foo(const Foo&) = delete;
   ^~~
main.cc:12:12: note: candidate: Foo<T>::Foo(T&&) [with T = std::vector<std::__cxx11::basic_string<char> >]
   explicit Foo(T&& x) { }
            ^~~
main.cc:11:12: note: candidate: Foo<T>::Foo(const T&) [with T = std::vector<std::__cxx11::basic_string<char> >]
   explicit Foo(const T& x) {  }

This test case works properly on clang (it resolves to the move constructor.
Comment 1 Jonathan Wakely 2018-03-13 18:46:20 UTC
(In reply to Jose Dapena Paz from comment #0)
> For a template class with explicit copy and move constructors,

Those aren't copy and move constructors.

Reduced:

struct X
{
  X(const char*) { }
};

template <typename T>
class Foo {
 public:
  template <typename... Args>
  explicit Foo(Args&&... args);

  explicit Foo(const T&);
  explicit Foo(T&&) { }

  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;

  ~Foo() = default;
};

int main() {
  Foo<X> v({"a"});
}



vs.cc: In function 'int main()':
vs.cc:22:17: error: call of overloaded 'Foo(<brace-enclosed initializer list>)' is ambiguous
   Foo<X> v({"a"});
                 ^
vs.cc:15:3: note: candidate: 'Foo<T>::Foo(const Foo<T>&) [with T = X]' <deleted>
   Foo(const Foo&) = delete;
   ^~~
vs.cc:13:12: note: candidate: 'Foo<T>::Foo(T&&) [with T = X]'
   explicit Foo(T&&) { }
            ^~~
vs.cc:12:12: note: candidate: 'Foo<T>::Foo(const T&) [with T = X]'
   explicit Foo(const T&);
            ^~~
Comment 2 Jonathan Wakely 2018-03-13 18:51:43 UTC
Further reduced:


struct X {
  X(int) { }
};

struct Foo {
  explicit Foo(const X&);
  explicit Foo(X&&) { }
};

int main() {
  Foo v({1});
}


vs.cc: In function 'int main()':
vs.cc:11:12: error: call of overloaded 'Foo(<brace-enclosed initializer list>)' is ambiguous
   Foo v({1});
            ^
vs.cc:7:12: note: candidate: 'Foo::Foo(X&&)'
   explicit Foo(X&&) { }
            ^~~
vs.cc:6:12: note: candidate: 'Foo::Foo(const X&)'
   explicit Foo(const X&);
            ^~~
vs.cc:5:8: note: candidate: 'constexpr Foo::Foo(const Foo&)'
 struct Foo {
        ^~~
vs.cc:5:8: note: candidate: 'constexpr Foo::Foo(Foo&&)'
Comment 3 Raphael Kubo da Costa 2018-10-11 10:37:04 UTC
+mpolacek in case he'd like to take a look. This is still reproducible with trunk.
Comment 4 Marek Polacek 2018-10-15 19:04:54 UTC
Thanks, this sounds interesting.
Comment 5 ensadc 2019-01-07 20:37:22 UTC
This is also ambiguous and seems to have the same cause (i.e. overload resolution accepts list-initialization that calls explicit constructor when forming the implicit conversion sequence):

template<class>
struct in_place_type_t { explicit in_place_type_t() = default; };

struct A { };

int f(A);
int f(in_place_type_t<A>);

int x = f({});
Comment 6 Zhihao Yuan 2019-01-13 04:44:58 UTC
Here is a possibly related case:

#include <tuple>

template <class T>
struct pair
{
    using value_type = pair<std::remove_reference_t<T>>;

    T a, b;

    constexpr pair& operator=(value_type const& other)
    {
        a = other.a;
        b = other.b;
        return *this;
    }

    constexpr pair& operator=(value_type&& other)
    {
        a = std::move(other.a);
        b = std::move(other.b);
        return *this;
    }
};

template <class T>
constexpr pair<T&> tie(T& a, T& b) noexcept
{
    return { a, b };
}

int main()
{
    int a = 3;
    int b = 5;
    tie(a, b) = { b, a % b };  // works
    tie(a, b) = { b, a };      // wat
}

Error messages are very similar https://godbolt.org/z/4FSeOO.
Comment 7 ensadc 2019-01-14 15:48:51 UTC
(In reply to Zhihao Yuan from comment #6)
> Here is a possibly related case:
> 
> [...]

I think this is a different bug. GCC thinks the implicitly-deleted move assignment operator `pair<int&>& pair<int&>::operator=(pair<int&>&&)` is a candidate for the assignment, which causes ambiguity with `operator=(value_type&&)` (where value_type = pair<int>). But as part of resolution of CWG 1402, [class.copy.assign] specifies that "A defaulted move assignment operator that is defined as deleted is ignored by overload resolution".
Comment 8 Andrew Pinski 2021-12-14 05:55:14 UTC
PR 97220 looks similar and might be a dup of this bug.
Comment 9 Jason Merrill 2023-05-31 20:59:52 UTC
I'm not marking this as a dup of PR60027 because of the specific case of copy/move constructors, which cannot be constrained to avoid this problem.  The patch in PR109247 fixes the first three testcases, which fall into that category, but not the testcase in #5, which does not.

The testcase in #6 is unrelated; there is no "explicit".

Unassigning from Marek.
Comment 10 Jason Merrill 2023-06-01 03:00:42 UTC
(In reply to ensadc from comment #7)
> (In reply to Zhihao Yuan from comment #6)
> 
> I think this is a different bug. GCC thinks the implicitly-deleted move
> assignment operator `pair<int&>& pair<int&>::operator=(pair<int&>&&)` is a
> candidate for the assignment, which causes ambiguity with
> `operator=(value_type&&)` (where value_type = pair<int>). But as part of
> resolution of CWG 1402, [class.copy.assign] specifies that "A defaulted move
> assignment operator that is defined as deleted is ignored by overload
> resolution".

Incidentally, no: GCC mentions it, but considers it worse than any other candidate.  The real ambiguity is with
operator=(const pair<int&>&), the implicitly-deleted *copy* assignment.  I don't think this is a bug; if you disagree, please open another PR for it.
Comment 11 Marek Polacek 2024-04-23 22:29:37 UTC
DR1228 was closed as NAD.