According to my understanding, the following two programs should be effectively identical, but the second fails to compile. Note that in this simplified example, the nesting is obviously useless, but it's necessary in the original context.
Version 1: works as expected
#include <iostream>
template<typename Value>
struct Anything {
Value value;
};
using A = const char *;
template<template<typename> class B>
struct Inner {
template<typename C>
using type = B<C>;
};
template<template<typename> class B>
struct OuterBase {
B<A> b;
void
print() { std::cout << "b: " << b.value << std::endl; }
};
using Outer = OuterBase<Inner<Anything>::type>; // this version works
int
main()
{
Outer outer;
outer.b.value = "foo";
outer.print();
return 0;
}
The compiler error in this version is "expected a template, got TypeBox<A>::Inner<Anything>::type
".
Version 2: fails with compile error
#include <iostream>
template<typename Value>
struct Anything {
Value value;
};
template<typename A>
struct TypeBox {
template<template<typename> class B>
struct Inner {
template<typename C>
using type = B<C>;
};
template<template<typename> class B>
struct OuterBase {
B<A> b;
void
print() { std::cout << "b: " << b.value << std::endl; }
};
using Outer = OuterBase<Inner<Anything>::type>; // compile error here
};
int
main()
{
using Box = typename TypeBox<const char *>::Outer;
Box box;
box.b.value = "foo";
box.print();
return 0;
}
I would expect the TypeBox
struct to be transparent when resolving the types inside, but according to gcc 12.2.1
, it isn't. Why not?
CodePudding user response:
The difference in your second example is that a compiler cannot verify at the point of definition of the template that Inner<Anything>::type
is really a class template or alias template. The problem is that Inner
is a nested class template and you could in theory partially or explicitly specialize Inner
for the given template argument later, so that type
names e.g. a type.
So compilers tend to require that you place the template
keyword before the template's name in such a case to state that it really is a template:
using Outer = OuterBase<Inner<Anything>::template type>;
My understanding is that technically there is nothing in the standard requiring this use of template
though, but that this wasn't entirely clear. However, the standard did allow using the template
keyword in this position, but will deprecate this allowance with C 23. If I am not mistaken compilers should also allow this without template
keyword in the future even for previous standard revisions (as defect report). There wasn't ever really a point in performing this test.
See also CWG issue 1478 and the paper proposing the deprecation P1787R6.