user12002570
user12002570

Reputation: 1

Clang accepts out of class destructor definition while gcc does not

I was writing an out-of-class destructor definition for a class template when I noticed that the program compiles with clang with c++17 and c++20 and also with gcc with c++17 but rejected with gcc c++20. Demo.

template<typename T>
struct C
{
    ~C(); 
};
template<typename T>
C<T>::~C<T>()           //accepted by compilers
{
    
}
int main()
{
    C<int> c;;
}

The result of the above program is summarized in the below table:

Compiler C++ Version Accepts-Code
GCC C++17 Yes
GCC C++20 No
GCC C++2b No
Clang C++17 Yes
Clang C++20 Yes
Clang C++2b Yes
MSVC C++17 Yes
MSVC C++20 Yes

As we can see in the above both of the compilers accept the code except that gcc with c++20 and onwards reject it with the error error: template-id not allowed for destructor.

So, my question is which compiler is right here(if any).

Upvotes: 4

Views: 543

Answers (2)

user12002570
user12002570

Reputation: 1

The program is ill-formed atleast starting from c++20 and clang and msvc are wrong in accepting the code with c++20 and onwards.

Note that the change in wording for class.dtor was introduced in C++23 via p1787r6-class.dtor and seems to be a DR for C++20.

So, the code is ill-formed from C++20 and onwards which can be seen from: class.dtor#1.2 which states that:

1 A declaration whose declarator-id has an unqualified-id that begins with a ~ declares a prospective destructor; its declarator shall be a function declarator ([dcl.fct]) of the form

 ptr-declarator ( parameter-declaration-clause ) noexcept-specifieropt attribute-specifier-seqopt 

where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:

1.2 otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier.

(emphasis mine)

And since the class-name is the injected-class-name C and not C<T> in our example, the correct way to write an out of class implementation for the destructor would be as shown below:

template<typename T>
struct C
{
    ~C(); //this is an ordinary destructor(meaning it is not templated)
};
template<typename T>
//-----v----------------> C is the injected-class-name and not C<T>
C<T>::~C()
{
    
}
int main()
{
    C<int> c;;
}

Demo

Here is the clang bug report:

Clang accepts invalid out of class definition for destructor

Here is msvc bug report:

MSVC accepts invalid out of class definition for a destructor of a class template with c++20


For further reading

One can also refer to:

Why destructor cannot be template?

Error: Out-of-line constructor cannot have template arguments C++.

template<typename T> someclass<T>::someclass<T>() is not allowed when providing an out of class definition for a constructor and a destructor with any c++ version.

Upvotes: 4

Jonathan Wakely
Jonathan Wakely

Reputation: 171263

I don't think it's true that this was invalid in C++17.

CWG 1435 introduced the wording that allowed the code (that wording can still be seen in the context for the [class.dtor] changes in CWG 2337):

in a declaration at namespace scope or in a friend declaration, the id-expression is nested-name-specifier ~class-name and the class-name names the same class as the nested-name-specifier."

This allows the code, because C<T>::~C<T> is an id-expression of that form. The class-name in the destructor ~C<T> names the same class as in C<T>::. That wording was present in C++17 and C++20.

P1787R6 Declarations and where to find them changed that wording to:

otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier."

This no longer allows ~C<T> because the injected-class-name is just C.

This seems like a breaking change that was not obvious from the revision history of the r5 paper, which includes:

  • Required destructor declarations to use the injected-class-name, avoiding name lookup
  • Simplified lookup for destructors
  • Specified that < begins a template argument list in a destructor name

All compilers (GCC, Clang, EDG and MSVC) accept the program in C++17 mode, and I don't think it's at all clear that the code was always invalid in older standards, as OP claims. It seems to be a possibly-unintentional change in C++23. Edit: I'm reliably informed by Jason Merrill that it was an intentional change for C++23, but not a DR for C++17. See his comment on the other answer.

Upvotes: 2

Related Questions