Bug 87016 - std::optional::operator= in constexpr context
Summary: std::optional::operator= in constexpr context
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 8.2.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-08-19 20:09 UTC by bobogu
Modified: 2018-08-20 15:35 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments
preprocessed TU (74.69 KB, text/plain)
2018-08-19 20:09 UTC, bobogu
Details

Note You need to log in before you can comment on or make changes to this bug.
Description bobogu 2018-08-19 20:09:48 UTC
Created attachment 44558 [details]
preprocessed TU

According to the standard, there is no assignment operator that should work in a constexpr context, however, I am able to successfully compile and use the following snippet:

// gcc -lstdc++ -std=c++17 main.cpp @Linux laptop 4.17.11-arch1 x86_64

constexpr std::optional<int> foo() {
    std::optional<int> bar = 3;
    bar = 10;
    return bar;
}
Comment 1 Jonathan Wakely 2018-08-20 10:26:10 UTC
The code is valid. The optional<T>::operator=(U&&) assignment operator is not used when is_scalar<T> and is_same<T, decay_T<U>> (which is true here, where T=int and U=int).

That means the assignment creates a temporary optional<int> using the constexpr constructor, i.e.

  bar = std::optional<int>(10);

This is valid in a constexpr function.
Comment 2 bobogu 2018-08-20 12:38:33 UTC
Do I understand it correctly that this will get optimized into one statement saying

  bar = std::optional<int>(10);

If so, is there something that could prevent such optimizations in order to check if the code is portable?

Also, just to clarify, this has nothing to do with this proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0602r3.html to which someone has pointed me to?
Comment 3 Jonathan Wakely 2018-08-20 12:52:21 UTC
(In reply to bobogu from comment #2)
> Do I understand it correctly that this will get optimized into one statement
> saying
> 
>   bar = std::optional<int>(10);

No, it's not an optimization.

When you assign an int to optional<int> it is equivalent to constructing a temporary optional<int> and then assigning that to the left-hand operand.

> If so, is there something that could prevent such optimizations in order to
> check if the code is portable?

It's not an optimization, it's the required behaviour according the the specification of std::optional in the standard.
 
> Also, just to clarify, this has nothing to do with this proposal
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0602r3.html to
> which someone has pointed me to?

It's nothing to do with that.
Comment 4 bobogu 2018-08-20 13:14:15 UTC
(In reply to Jonathan Wakely from comment #3)
> (In reply to bobogu from comment #2)
> When you assign an int to optional<int> it is equivalent to constructing a
> temporary optional<int> and then assigning that to the left-hand operand.

I feel stupid, but I still don't understand how that can work in a constexpr context when the current c++17 standard doesn't specify any constexpr assignment operator. 

The second line of this snippet
  std::optional<int> bar = 3;
  bar = std::optional<int>(10);

would call the 3rd assignment operator (https://en.cppreference.com/w/cpp/utility/optional/operator%3D), right? And the operator isn't constexpr, but works in a constexpr function.
Comment 5 Jonathan Wakely 2018-08-20 13:34:32 UTC
(In reply to bobogu from comment #4)
> I feel stupid, but I still don't understand how that can work in a constexpr
> context when the current c++17 standard doesn't specify any constexpr
> assignment operator. 

That's true, but the standard is broken.

All implementations define the assignment operator as defaulted, and so the compiler makes it constexpr.

In fact the p0602R3 proposal you linked to is relevant, because it would *require* implementations to define the operator as defaulted (in order to be trivial) and so the compiler is always going to make it constexpr for std::optional<int>.

Anyway, I don't consider "I can use more things in constant expressions than the standard says" to be a bug.
Comment 6 Jonathan Wakely 2018-08-20 14:03:21 UTC
(In reply to Jonathan Wakely from comment #5)
> In fact the p0602R3 proposal you linked to is relevant, because it would
> *require* implementations to define the operator as defaulted (in order to
> be trivial) and so the compiler is always going to make it constexpr for
> std::optional<int>.

I've raised this with the standards committee.
Comment 7 bobogu 2018-08-20 14:30:28 UTC
(In reply to Jonathan Wakely from comment #5)
> (In reply to bobogu from comment #4)
> All implementations define the assignment operator as defaulted, and so the
> compiler makes it constexpr.
> 
> In fact the p0602R3 proposal you linked to is relevant, because it would
> *require* implementations to define the operator as defaulted (in order to
> be trivial) and so the compiler is always going to make it constexpr for
> std::optional<int>.

Ah, finally it makes sense to me!

> Anyway, I don't consider "I can use more things in constant expressions than
> the standard says" to be a bug.

All right, I'm sorry then... I just thought that as this is undocumented, it could lead to producing an unportable code.

Anyway, thank you for clearing it out.

> I've raised this with the standards committee.
Thanks!
Comment 8 Jonathan Wakely 2018-08-20 15:11:03 UTC
(In reply to bobogu from comment #7)
> All right, I'm sorry then... I just thought that as this is undocumented, it
> could lead to producing an unportable code.

It could, except that you should get the same behaviour for every correct implementation. Your example doesn't compile with libc++, but that's a bug:
https://bugs.llvm.org/show_bug.cgi?id=38638
Comment 9 bobogu 2018-08-20 15:35:39 UTC
(In reply to Jonathan Wakely from comment #8)
> (In reply to bobogu from comment #7)
> > All right, I'm sorry then... I just thought that as this is undocumented, it
> > could lead to producing an unportable code.
> 
> It could, except that you should get the same behaviour for every correct
> implementation. Your example doesn't compile with libc++, but that's a bug:
> https://bugs.llvm.org/show_bug.cgi?id=38638

Yes, that was just my misconception before you got me on the right track - I thought clang's behaviour was correct because of what I saw in the standard :-)