5

GCC 7.2 and Clang 5.0 do not agree in this case:

struct A;

A foo();

struct A
  {
  static void bar()
     {
     foo();
     }
  private:
  ~A()=default;
  };

A foo()
  {
  return {};
  //GCC error: A::~A() is private in this context.
  };

This behavior is part of the "c++17 guaranteed (copy elision and is not related RVO or NRVO)."

GCC does not compile this code but Clang does. Which one is wrong?

Maybe this paragraph says that the bot Clang and GCC are standard compliant [class.temporary]:

When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function’s parameter or return object is initialized as if by using the non-deleted trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object). [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note ]

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • 1
    In your case, there will be a materialization conversion, so the destructor mus be accessible. The rule for discarded value expressions like "f()" is: "If the (possibly converted) expression is a prvalue, the temporary materialization conversion is applied." – Johannes Schaub - litb Oct 03 '17 at 14:47
  • @JohannesSchaub-litb Thank you, I am going to read it carefully! – Oliv Oct 03 '17 at 15:24
  • 1
    And of course, the destructor *is* accessible, because the destruction happens inside of `A`. So this code is valid even if there's a materialization conversion. The issue is, in fact, entirely with the text you quoted. I think it allows to both accept or reject your code. Because there will be a temporary constructed in `foo`. Since the text contains not well defined terms, I recommend reading the issue report at http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1590 for it. – Johannes Schaub - litb Oct 03 '17 at 15:24
  • @JohannesSchaub-litb Sorry for the edit, your first comment fited the first question version. Thank you for the link, sure it will enlight me! – Oliv Oct 03 '17 at 15:27
  • @Olive it was my mistake, because I thought that "bar" has no access to the destructor. But of course it has. So it doesn't matter whether or not it calls "foo". – Johannes Schaub - litb Oct 03 '17 at 15:29
  • What I am *not* sure about is in what context the temporary is created (inside of "foo" or inside of "bar"). From the issue, the rationale was to allow returning objects inside of registers. I think this means that the problem is at the call-side - the register value needs to be copied into the stack (temporary materialized object). So the copy would happen in the context of the caller, and *not* in the callee. Which would mean that regardless, your code would be *well-formed*. That's the reason this issue is still active, because it's way too unclear. – Johannes Schaub - litb Oct 03 '17 at 15:33
  • @JohannesSchaub-litb You are so right, I am impressed! I have tested it: https://godbolt.org/g/QmrBNV Clang perform the optimization of returning the value from the register when there is a trivial copy constructor, and then complains about temporary destructor in the callee!! So for Clang the question is done! – Oliv Oct 03 '17 at 15:54
  • ...I'm confused, in `return A{}` the operand is a prvalue ( and hence should be used to initialize the A in bar(), no copy ) and clang complains of ~A inaccessibility in foo; whereas in `return {}` the operand is not a prvalue and clang does not complain ... ?? – Massimiliano Janes Oct 03 '17 at 16:04
  • @MassimilianoJanes i don't understand why it complains about "return A{};" either. IMO it should be the same as "return {}" in that regard. – Johannes Schaub - litb Oct 03 '17 at 16:11
  • @JohannesSchaub-litb yep, actually, I would have expected "return A{}" to compile ok and "return {}" not, because I would have thought that the resulting (possible) temporary would be in foo scope in the latter case... – Massimiliano Janes Oct 03 '17 at 16:14

2 Answers2

1

I believe this is a clang bug.

From [class.temporary]:

Temporary objects are created [...] when needed by the implementation to pass or return an object of trivially-copyable type (see below), and [...]

Even when the creation of the temporary object is unevaluated ([expr.prop]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed. [ Note: This includes accessibility and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of the operand of a decltype-specifier ([expr.call]), no temporary is introduced, so the foregoing does not apply to such a prvalue. — end note ]

The copy-initialization of the return of foo is a context that creates a temporary object, so the semantic restrictions must still be followed - which include the accessibility of the destructor (as the note helps make clear). ~A() must be accessible in this context, and isn't, so the program should be ill-formed. gcc is correct to reject.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
0

I believe this is a gcc bug in C++17. According to copy elision:

since C++17

Under the following circumstances, the compilers are required to omit the copy- and move- construction of class objects even if the copy/move constructor and the destructor have observable side-effects. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.

In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.

Your function foo returns a prvalue object of type A and the return type of foo is A, no matter whether A has accessible copy/move constructor and destructor, the copy will be omitted in C++17.

Community
  • 1
  • 1
JiaHao Xu
  • 2,452
  • 16
  • 31