I would like to declare nested template classes but define the constructor and member functions of the most inner class outside the class definition (for reasons which are specific to a project I am working on, but it's mainly to reduce compilation time and memory at scale).
Some compilers parse the class definition differently and seem to follow different standards. Especially, g++ in c++20 standard mode says that the class identifier in the external definition of the constructor is a dependent name that must be preceded by the typename keyword, which seems weird to me since it's a class identifier, not a type at this point. Strangely, the compiler behaviour is different for external member function definitions, for which g++ does not complain about the same use of the class identifier when defining the member function code.
Making g++ in c++20 mode happy prevents in return from compiling the code on msvc. I would like to understand the standards and to find a robust external definition of nested template class constructors.
The following code compiles fine on g++ in c++-17 mode, as well on clang and msvc in c++-20 mode, but not with g++ in c++-20 mode:
#include <iostream>
template <typename T> struct A {
struct B {
template <typename TT> struct C {
C();
void foo();
};
};
};
template <typename T>
template <typename TT>
A<T>::B::C<TT>::C() {
std::cout << "constructor" << std::endl;
}
template <typename T>
template <typename TT>
void A<T>::B::C<TT>::foo() {
std::cout << "foo" << std::endl;
}
int main()
{
A<int>::B::C<int> c;
c.foo();
}
Strangely, g++ can parse the definition of A<T>::B::C<TT>::foo() outside the class, but not the definition of the constructor A<T>::B::C<TT>::C().
It returns the following compilation error:
<source>:15:10: error: non-template 'C' used as template
15 | A<T>::B::C<TT>::C() {
| ^
<source>:15:10: note: use 'typename A<T>::B::template C' to indicate that it is a template
<source>:15:1: error: need 'typename' before 'A<T>::B::C' because 'A<T>::B' is a dependent scope
15 | A<T>::B::C<TT>::C() {
| ^~~~
| typename
Compiler returned: 1
However, if I change the definition of the constructor above by the following lines:
template <typename T>
template <typename TT>
A<T>::B::template C<TT>::C() {
std::cout << "constructor" << std::endl;
}
then it compiles with g++ and clang in both c++17 and c++20 modes but not with msvc. Note that the external definition of void A<T>::B::C<TT>::foo() does not need to be changed for some reason.
Which compiler is right regarding the standards? How to make this code robust to g++, clang and msvc parsers in C++20 mode?
How to make this code robust to g++, clang and msvc parsers in C++20 mode?
You need to use template keyword(for the dependent template name C) when providing the out of class definition for the ctor and the member function as shown below:
//other code as before
template <typename T>
template <typename TT>
//-------vvvvvvvv--------------------->use template keyword as dependent template name
A<T>::B::template C<TT>::C() {
std::cout << "constructor" << std::endl;
}
template <typename T>
template <typename TT>
//------------vvvvvvvv-------------------->use template keyword as dependent template name
void A<T>::B::template C<TT>::foo() {
std::cout << "foo" << std::endl;
}
Working demo
Note that msvc rejects the above code in both c++17 and c++20 which is a msvc bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With