Bug 84782 - Rejects a maybe C++ code snippet
Summary: Rejects a maybe C++ code snippet
Status: RESOLVED DUPLICATE of bug 89381
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 8.0.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: rejects-valid
Depends on:
Blocks:
 
Reported: 2018-03-09 14:02 UTC by Martin Liška
Modified: 2020-02-05 10:21 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2018-03-12 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Martin Liška 2018-03-09 14:02:55 UTC
We reject:

$ cat playback_image_provider.ii
template <typename a, a b> struct c { static constexpr a d = b; };
template <typename a> a ac();
template <typename a, typename e> class f {
  template <typename g, typename, typename = decltype(ac<g>() = ac)>
  void h(int);
  template <typename, typename> static c<bool, false> h(...);

public:
  typedef decltype(h<a, e>(0)) i;
};
template <typename a> struct j : f<a, int>::i {};
class k {
public:
  k(const k &);
  void operator=(k &&);
};
class l : k {
  using ae = k;
  using ae::operator=;
};
template <int> struct m {};
template <typename af> class n : m<j<af>::d> {};
class o {
  struct G {
    G(const G &);
    l q;
  };
  n<G> p;
};
o::G::G(const G &) = default;

$ g++ -c playback_image_provider.ii
playback_image_provider.ii:30:1: note: ‘o::G::G(const o::G&)’ is implicitly deleted because the default definition would be ill-formed:
 o::G::G(const G &) = default;
 ^
playback_image_provider.ii:30:1: error: use of deleted function ‘l::l(const l&)’
playback_image_provider.ii:17:7: note: ‘l::l(const l&)’ is implicitly declared as deleted because ‘l’ declares a move constructor or move assignment operator
 class l : k {
       ^

However clang accepts:

$ clang++ -c playback_image_provider.ii -std=c++11
Comment 1 Jonathan Wakely 2018-03-12 16:46:47 UTC
Reduced:

struct c { static constexpr bool value = false; };

template <typename a> a declval();

template <typename g, typename = decltype(declval<g>() = 1)>   // #1
  void h(int);

template <typename>
  c h(...);

template<typename T> using i = decltype(h<T>(0));

struct k {
  k(const k &) = default;
  void operator=(k &&) = delete;
};
struct l : k {
  using k::operator=;   // #2
};

struct G {
  G(const G &);
  l q;
};

int j = i<G>::value;

G::G(const G &) = default;



x.cc:28:1: note: ‘G::G(const G&)’ is implicitly deleted because the default definition would be ill-formed:
 G::G(const G &) = default;
 ^
x.cc:28:1: error: use of deleted function ‘constexpr l::l(const l&)’
x.cc:17:7: note: ‘constexpr l::l(const l&)’ is implicitly declared as deleted because ‘l’ declares a move constructor or move assignment operator
 class l : k {
       ^


The error is malformed for a start, we issue a note that doesn't follow any error.

There seem to be two things involved here. The error is apparently triggered by the SFINAE check at #1 which should silently fail instead of giving an error. It seems to be an error because of the "using k::operator=;" line at #2 which should have no effect.
Comment 2 Jonathan Wakely 2018-03-12 17:00:35 UTC
Slight correction: the using-declaration doesn't have no effect in general, but it shouldn't change the result of this program.
Comment 3 Raphael Kubo da Costa 2018-03-21 15:52:38 UTC
Given the file name used in the original report and how this bug was mentioned in the chromium-packager mailing list, I think this is a duplicate of bug 70431.
Comment 4 Jonathan Wakely 2018-03-21 16:18:50 UTC
They don't look like duplicates to me. PR 70431 involves a union, no SFINAE, and a completely different error message.
Comment 5 Raphael Kubo da Costa 2018-03-22 09:50:33 UTC
Sorry if my comment was too coarse-grained. My hypothesis that this is a duplicate comes from playback_image_provider.ii looking like Chromium's playback_image_provider.cc, which was failing to build with GCC when a copy constructor was not defined inline (just like the union from bug 70431).

My reduced testcase from the original Chromium code (without templates) looks like this:

-----
struct S1 {
  S1& operator=(const S1&) = default;
  S1& operator=(S1&&) = default;
};

struct S2 {
  S2() = default;
  S2(const S2&);
  S1 m;
};

S2::S2(const S2&) = default;
-----

x.cc:12:1: note: ‘S2::S2(const S2&)’ is implicitly deleted because the default definition would be ill-formed:
 S2::S2(const S2&) = default;
 ^~
x.cc:12:1: error: use of deleted function ‘constexpr S1::S1(const S1&)’
x.cc:1:8: note: ‘constexpr S1::S1(const S1&)’ is implicitly declared as deleted because ‘S1’ declares a move constructor or move assignment operator
 struct S1 {
        ^~

The error goes away if S2's copy constructor is declared inline.
Comment 6 Jonathan Wakely 2018-03-22 12:01:09 UTC
(In reply to Raphael Kubo da Costa from comment #5)
> Sorry if my comment was too coarse-grained. My hypothesis that this is a
> duplicate comes from playback_image_provider.ii looking like Chromium's
> playback_image_provider.cc, which was failing to build with GCC when a copy
> constructor was not defined inline (just like the union from bug 70431).

Not every example with a non-inline copy constructor is the same though.

> My reduced testcase from the original Chromium code (without templates)
> looks like this:
> 
> -----
> struct S1 {
>   S1& operator=(const S1&) = default;
>   S1& operator=(S1&&) = default;
> };
> 
> struct S2 {
>   S2() = default;
>   S2(const S2&);
>   S1 m;
> };
> 
> S2::S2(const S2&) = default;
> -----
> 
> x.cc:12:1: note: ‘S2::S2(const S2&)’ is implicitly deleted because the
> default definition would be ill-formed:
>  S2::S2(const S2&) = default;
>  ^~
> x.cc:12:1: error: use of deleted function ‘constexpr S1::S1(const S1&)’
> x.cc:1:8: note: ‘constexpr S1::S1(const S1&)’ is implicitly declared as
> deleted because ‘S1’ declares a move constructor or move assignment operator
>  struct S1 {
>         ^~
> 
> The error goes away if S2's copy constructor is declared inline.

Because that's what the C++ standard requires. A copy constructor that is defined as defaulted outside the class body is ill-formed if it would be implicitly deleted. If it's defaulted on its first declaration (i.e. inside the class body) then it is defined as deleted.

Your example is not valid, and is rejected by GCC and Clang and EDG.
Comment 7 Raphael Kubo da Costa 2018-03-22 12:35:22 UTC
(In reply to Jonathan Wakely from comment #6)
> Your example is not valid, and is rejected by GCC and Clang and EDG.

Ugh, I forgot to test it with clang before posting my comment. I stand corrected. 

Is it relevant that your testcase builds fine when G's copy constructor is inlined?
Comment 8 Jonathan Wakely 2018-03-22 15:38:22 UTC
(In reply to Raphael Kubo da Costa from comment #7)
> Is it relevant that your testcase builds fine when G's copy constructor is
> inlined?

Yes, it seems to be. The checks done for the out-of-class defaulted definition fail for bogus reasons.
Comment 9 Martin Liška 2018-03-27 08:51:24 UTC
I can confirm that the PR is blocking Chromium build in openSUSE.
Do I understand that correctly Jonathan that it's an issue in GCC? Or is it an invalid code snippet?
Comment 10 Raphael Kubo da Costa 2018-03-27 08:55:33 UTC
(In reply to Martin Liška from comment #9)
> I can confirm that the PR is blocking Chromium build in openSUSE.

From a Chromium perspective (where I'm coming from), it shouldn't be. The problematic bits were changed in Chromium in https://chromium-review.googlesource.com/c/chromium/src/+/944403 because of this bug and the commit just be backported to unblock your package builds.
Comment 11 Jonathan Wakely 2018-03-27 11:04:25 UTC
(In reply to Martin Liška from comment #9)
> Do I understand that correctly Jonathan that it's an issue in GCC? Or is it
> an invalid code snippet?

This is a GCC bug. The code should be accepted without an error.
Comment 12 Raphael Kubo da Costa 2020-02-05 09:48:01 UTC
FWIW, the snippet from comment #1 builds fine with GCC >= 9.1 at least.
Comment 13 Jonathan Wakely 2020-02-05 10:21:39 UTC
It was fixed by:

    PR c++/89381 - implicit copy and using-declaration.
    
    Here the used base<int>::operator= gets into the list of foo's bindings for
    operator=, but it shouldn't make the copy ctor deleted.
    
            * class.c (classtype_has_move_assign_or_move_ctor_p): Don't consider
            op= brought in by a using-declaration.
    
    From-SVN: r269442

Looks like a dup of PR c++/89381.

That fix has also been backported for GCC 8.4

*** This bug has been marked as a duplicate of bug 89381 ***