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

Non-constexpr destructor removes constexpr-ness for defaulted assignment operators #527

Open
Ukilele opened this issue Apr 26, 2023 · 2 comments

Comments

@Ukilele
Copy link

Ukilele commented Apr 26, 2023

According to https://eel.is/c++draft/dcl.fct.def.default#3, a copy-/move-assignment operator, that is defaulted on its first declaration, is constexpr if possible. And according to https://eel.is/c++draft/dcl.constexpr#3, it is possible to be constexpr when it is not a coroutine. So in all the examples below (A, B, C, D, D<true>, D<false>) I would expect the assignment operators to be constexpr. But cppinsights only marks the ones of A, C and D<true> as constexpr. Is the constexpr-ness in the cases B, D and D<false> missing? Or do I miss something?

Source code:

struct A {
  ~A() = default;
  A& operator=(const A&) = default;
  A& operator=(A&&) = default;
};

struct B {
  ~B() {}
  B& operator=(const B&) = default;
  B& operator=(B&&) = default;
};

struct C {
  constexpr ~C() {}
  C& operator=(const C&) = default;
  C& operator=(C&&) = default;
};

template<bool Constexpr>
struct D {
  constexpr ~D() requires Constexpr {}
  ~D() requires (!Constexpr) {}
  D& operator=(const D&) = default;
  D& operator=(D&&) = default;
};

template struct D<true>;
template struct D<false>;

Generated insights:

struct A
{
  inline constexpr ~A() /* noexcept */ = default;
  inline constexpr A & operator=(const A &) /* noexcept */ = default;
  inline constexpr A & operator=(A &&) /* noexcept */ = default;
  // inline constexpr A(const A &) /* noexcept */ = delete;
};



struct B
{
  inline ~B() noexcept
  {
  }
  
  inline B & operator=(const B &) /* noexcept */ = default;
  inline B & operator=(B &&) /* noexcept */ = default;
  // inline constexpr B(const B &) /* noexcept */ = delete;
};



struct C
{
  inline constexpr ~C() noexcept
  {
  }
  
  inline constexpr C & operator=(const C &) /* noexcept */ = default;
  inline constexpr C & operator=(C &&) /* noexcept */ = default;
  // inline constexpr C(const C &) /* noexcept */ = delete;
};



template<bool Constexpr>
struct D
{
  inline constexpr ~D() requires Constexpr
  {
  }
  
  inline ~D() requires (!Constexpr)
  {
  }
  
  inline D<Constexpr> & operator=(const D<Constexpr> &) = default;
  inline D<Constexpr> & operator=(D<Constexpr> &&) = default;
};





template<>
struct D<true>
{
  inline constexpr ~D() noexcept requires true
  {
  }
  
  inline ~D() /* noexcept */ requires (!true);
  
  inline constexpr D<true> & operator=(const D<true> &) /* noexcept */ = default;
  inline constexpr D<true> & operator=(D<true> &&) /* noexcept */ = default;
  // inline constexpr D(const D<true> &) /* noexcept */ = delete;
};





template<>
struct D<false>
{
  inline constexpr ~D() /* noexcept */ requires false;
  
  inline ~D() noexcept requires (!false)
  {
  }
  
  inline D<false> & operator=(const D<false> &) /* noexcept */ = default;
  inline D<false> & operator=(D<false> &&) /* noexcept */ = default;
  // inline constexpr D(const D<false> &) /* noexcept */ = delete;
};


@andreasfertig
Copy link
Owner

Hello @Ukilele,

that looks like an interesting question. From the paragraphs you quote, I agree your interpretation seems correct.

If we look at the AST from Clang (compiler-explorer.com/z/K3f5jzscE) we can see that B's operator is not constexpr. The behavior of C++ Insights matches what Clang thinks.

Now, there is one piece missing, which I couldn't find quickly. It does not make sense to mark the assignment operators constexpr in B since we can never use them in a constexpr context. For example, the compilation fails if you uncomment line 13 in the Compiler Explorer link.

I can see that eel.is/c++draft/dcl.constexpr#3 does not say that, which is confusing. I'll ask WG21 whether there is some text missing or there is a sub-clause somewhere addressing this behavior.

Andreas

@Ukilele
Copy link
Author

Ukilele commented Apr 28, 2023

Hi Andreas,
thanks a lot for your comprehensive response.

I agree that B is a contrived example. Having a class with a constexpr assignment operator but with a non-constexpr destructor is at most useful for theoretical discussions. I just was playing around on cppinsights, because it was not completely clear to me when a defaulted special member function is constexpr or noexcept and your tool is very helpful to find that out!

The reason that line 13 fails to compile is, because the destructor of B is user-provided (showstopper for C++17) and not marked as constexpr (showstopper for C++20). But you are right, I think we can never actually use them in a constexpr context, because we can't use B in a constexpr context, as it's destructor is not constexpr.

So far I still think that the assignment operators of B, D and D<false> should be marked constexpr. But I see that the issue would be on Clang and not on cppinsights.

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

2 participants