Bug 91742 - User defined conversion references
Summary: User defined conversion references
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 7.1.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: accepts-invalid
Depends on:
Blocks:
 
Reported: 2019-09-11 15:52 UTC by Diego Franco
Modified: 2020-01-29 12:16 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2020-01-29 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Diego Franco 2019-09-11 15:52:45 UTC
The following code does not work as intended:

//----Example-----

#include <vector>
#include <cassert>

struct A
{
  operator const std::vector<int>&() const
  {
    return a_;
  }

  std::vector<int> a_;
};

int main()
{
  A a {};

  const auto& b1 {static_cast<const std::vector<int>&>(a)};
  const std::vector<int>& b2 {a};

  assert(&a.a_ == &b1);
  assert(&b1 == &b2);  // does not work with gcc 8.3.0 any standard
                       // works with gcc 7.1.0 with c++17 only                     
}

//----End of example-----

I read through the c++17 standard and did not find any information about this behavior being implementation defined.

Diego
Comment 1 Diego Franco 2019-09-11 15:53:55 UTC
The code does work when changing std::vector<int> for any primitive types, i.e. int.
Comment 2 Jonathan Wakely 2019-09-11 16:44:07 UTC
(In reply to Diego from comment #0)
> I read through the c++17 standard and did not find any information about
> this behavior being implementation defined.

It's not implementation defined, but I think all implementations are required to do what GCC 8 does (and other compilers agree).

The current behaviour started with r259123 (before that there  was an ICE, introduced by r258755).
Comment 3 Jonathan Wakely 2019-09-11 16:46:16 UTC
Reduced:

#define assert(C) if (!(C)) { __builtin_puts("Assertion failed: " #C); __builtin_abort(); }

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

struct A
{
  operator const X&() const
  {
    return a_;
  }

  X a_;
};

int main()
{
  A a {};

  const auto& b1 {static_cast<const X&>(a)};
  const X& b2 {a};

  assert(&a.a_ == &b1);
  assert(&b1 == &b2);  // does not work with gcc 8.3.0 any standard
}

Assertion failed: &b1 == &b2
Aborted (core dumped)

If "const X& b2 (a)" is used instead of list-init, the second assertion passes.
Comment 4 Diego Franco 2019-09-11 17:17:33 UTC
> It's not implementation defined, but I think all implementations are required to do what GCC 8 does (and other compilers agree).

It worked on linaro 7.4.1 gcc with c++17, gcc 7.1.0 with c++17 only.

> If "const X& b2 (a)" is used instead of list-init, the second assertion passes.

That seems like an issue no working with {}.
Comment 5 Jonathan Wakely 2019-09-11 17:37:56 UTC
(In reply to Diego Franco from comment #4)
> It worked on linaro 7.4.1 gcc with c++17, gcc 7.1.0 with c++17 only.

Yes, sometimes old versions have incorrect behaviour and they get fixed.

I agree this is surprising, but it's not the only weird property of {} initialization.
Comment 6 Diego Franco 2019-09-11 17:41:12 UTC
Also the brace initialization works with primitive types for the code I posted in the first place. That's definitely a code smell.
Comment 7 Diego Franco 2019-09-11 17:43:41 UTC
This works:

#include <vector>
#include <cassert>

struct A
{
  operator const int&() const
  {
    return a_;
  }

  int a_;
};

int main()
{
  A a {};

  const auto& b1 {static_cast<const int&>(a)};
  const int& b2 {a};

  assert(&a.a_ == &b1);
  assert(&b1 == &b2);  // works
Comment 8 Diego Franco 2019-09-12 12:53:21 UTC
So to summarize, these are the main reason why I believe this should be addressed:

- init brace works for references of any type:

std::vector<int> a {};
std::vector<int>& b {a};
assert(&a == &b); // works

int c {};
int& d {c};
assert(&c == &d); // works

- init brace works for user defined conversion reference when using static cast:

class A { 
  operator const std::vector<int>&() const {return a_;} 
  std::vector<int> a_; 
};

A a {};
const auto& b {static_cast<const std::vector<int>&>(a)};
assert(&a == &b); // works

- init brace does not work for user defined conversion reference WITHOUT static cast:

A a {};
const auto& b {a};
assert(&a == &b); // does not work


I think the above behavior is quite unexpected, and does not follow any logic of other behaviors in the language. I found this unexpected behavior by running unit tests. Also, semantic wise is correct to use user-defined-conversion without static cast (above example).
Comment 9 Diego Franco 2019-09-12 13:01:40 UTC
Correction/editing of last section of former comment:

...
- init brace does not work for user defined conversion reference WITHOUT static cast:

A a {};
const std::vector<int>& b {a}; // changed from "auto" to "std::vector<int>"
assert(&a == &b); // does not work
...
...