Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

constexpr moves #35

Open
jlaire opened this issue Nov 3, 2015 · 5 comments
Open

constexpr moves #35

jlaire opened this issue Nov 3, 2015 · 5 comments

Comments

@jlaire
Copy link
Contributor

jlaire commented Nov 3, 2015

Optional is awesome and I'd like to return an optional<int> from a constexpr function. The following function compiles with g++ 5.2.0, but not with clang 3.7.0.

$ clang++ -std=c++1z -x c++ -c - <<< '
#include "optional.hpp"
constexpr std::experimental::optional<int> f1() { return 5; }
'

<stdin>:3:44: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr std::experimental::optional<int> f1() { return 5; }
                                           ^
<stdin>:3:51: note: non-constexpr constructor 'optional' cannot be used in a constant expression
constexpr std::experimental::optional<int> f1() { return 5; }
                                                  ^
./optional.hpp:424:3: note: declared here
  optional(optional&& rhs) noexcept(is_nothrow_move_constructible<T>::value)
  ^

I found a mention of this in the rationale:

[..] we know an efficient implementation of such optional with compile-time interface — except for copy constructor and move constructor — is possible.

If optionals can't be returned, their usefulness in constexpr functions seems unfortunately very limited to me.

As a temporary workaround, I tried adding constexpr to the move constructor, and both g++ and clang accepted it. Has the definition of constexpr functions been relaxed so that this is possible, or am I relying on overly permissible compilers?

@akrzemi1
Copy link
Owner

akrzemi1 commented Nov 3, 2015

2015-11-03 17:15 GMT+01:00 Johannes Laire [email protected]:

Optional is awesome and I'd like to return an optional from a
constexpr function. The following function compiles with g++ 5.2.0, but not
with clang 3.7.0.

$ clang++ -std=c++1z -x c++ -c - <<< '
#include "optional.hpp"
constexpr std::experimental::optional f1() { return 5; }
'

:3:44: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr std::experimental::optional f1() { return 5; }
^
:3:51: note: non-constexpr constructor 'optional' cannot be used in a constant expression
constexpr std::experimental::optional f1() { return 5; }
^
./optional.hpp:424:3: note: declared here
optional(optional&& rhs) noexcept(is_nothrow_move_constructible::value)
^

I found a mention of this in the rationale
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3527.html#rationale.constexpr
:

[..] we know an efficient implementation of such optional with
compile-time interface — except for copy constructor and move constructor —
is possible.

If optionals can't be returned, their usefulness in constexpr functions
seems unfortunately very limited to me.

As a temporary workaround, I tried adding constexpr to the move
constructor, and both g++ and clang accepted it. Has the definition of
constexpr functions been relaxed so that this is possible, or am I relying
on overly permissible compilers?

An interesting question. The requirements for the constexpr constructor
have been relaxed. But does this relaxation suffice? From the fast reading
of the Standard, I cannot find a definite answer. The reference
implementation has the following definition:

optional(optional&& rhs) noexcept(is_nothrow_move_constructible::value)
: OptionalBase(only_set_initialized, false)
{
if (rhs.initialized()) {
::new (static_cast<void*>(dataptr())) T(std::move(*rhs));
OptionalBase::init_ = true;
}
}

Can the body of the constexpr constructor alter the value of class members?
I didn't find this to e described explicitly (at least on the fast reading)

Regards,
&rzej

@jlaire
Copy link
Contributor Author

jlaire commented Nov 12, 2015

Thanks for the reply. I have to admit that this stuff is a little bit out of my depth.

Perhaps guaranteed copy elision will resolve this for C++17 even if the move ctor can't be constexpr.

g++'s behavior might be a bug though. Since a non-movable type can't be returned by value from a function, it would make sense that a non-constexpr-movable type can't be returned by value from a constexpr function.

@akrzemi1
Copy link
Owner

I am not sure if it helps anything in practice, but you can return non-moveable types by value with a bit of effort:

#include <cassert>

struct Guard
{
    int v;
    Guard(const Guard&) = delete;
    Guard(Guard&&) = delete;
};

Guard makeGuard()
{
    return {7};
}

int main()
{
    Guard&& g = makeGuard();
    assert (g.v == 7);
}

You can try it out:
http://melpon.org/wandbox/permlink/v1aWbdmfDgvZl4V4

@gnzlbg
Copy link

gnzlbg commented Mar 9, 2016

The same is true for the copy-constructor, it is also not constexpr.

IMO, the whole optional interface should be constexpr for literal types(*). The best way to achieve it would be to special-case OptionalBase for literal types.

(*) Ideally one should special case copy-constructor, move-constructor, destructor, constructor... depending on whether the type is trivially copy-constructible/moveconstructible/destructible/constructibole/... but this explodes into a mess of base classes.

I would say, keep it simple. Make the whole optional interface conditionally constexpr and leave to the OptionalBase class whether the tricky operations (destruction, copy/move construction/assignment) are actually constexpr. Then just specialize the base-class once for literal types. Omit the destructor in the specialization (constexpr destructors must be implicitly generated), and omit the use of placement new in the other operations.

Having to special-case OptionalBase for literal/non-literal types is not optimal, but it is the best we can do in C++11, and doing so in C++14 is actually easy as long as one does not need placement new/reinterpret cast, and/or initializer_lists.

@akrzemi1
Copy link
Owner

Yes, it should be doable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants