In the next example the class U
with private destructor has a friend function foo
. And this friend function has argument of type U
with default value U{}
:
class U{ ~U(); friend void foo(U); };
void foo(U = {});
Clang and MSVC accept this code, but GCC rejects it with the error
error: 'U::~U()' is private within this context
2 | void foo(U = {});
| ^
Demo: https://gcc.godbolt.org/z/eGxYGdzj3
Which compiler is right here, and does friendship extend on default arguments in C++?
Best Answer
C++20 [class.access]/8 provides as follows:
However, [expr.call]/8 says:
While the "Example" text is not normative, I believe it reflects the intent; therefore, in order to read these two provisions harmoniously, we should understand that the destructor of the type of the default argument is (in my opinion, at least) not a name "in a default argument". Instead, we should view the call to the friend function as occurring in the following stages:
GCC shouldn't be rejecting the declaration
void foo(U = {})
as there is no actual use of the destructor yet; and indeed, it is possible thatfoo
might be called only from contexts that have access toU::~U
. But iffoo
is called from a context that doesn't have access toU::~U
, the program should be ill-formed. In such cases, I think that Clang and MSVC are wrong, because they still accept the code.However, there is also the issue of [dcl.fct.default]/5 which states:
The standard never defines what it means by "semantic constraints"; if it's assumed to include access control for both the initialization and destruction, then that might explain why Clang and MSVC seem to allow calls to
foo
from contexts that ought not to have access toU::~U
.But thinking about this more, I feel that this doesn't make too much sense, because it would imply that default arguments are "special" in a way that I don't think was intended. To wit, consider:
Here, MSVC accepts both lines 1 and 2; it seems clearly wrong to accept line 1, considering how [expr.call]/8 requires the destructor to be accessible from
main
. But Clang accepts line 2 and rejects line 1, which also seems absurd to me: I don't feel that the intent of the standard was that choosing to use the default argument (as opposed to providing the argument yourself) would exempt the caller from having to have access to the destructor of the parameter type.If [dcl.fct.default]/5 appears to require Clang's behaviour, then I believe that it should be considered defective.