The following is invalid code:
struct foo {
struct bar;
bar x; // error: field x has incomplete type
struct bar{ int value{42}; };
};
int mai
I think this example is explicitly allowed by
17.6.1.2 Member classes of class templates [temp.mem.class]
1 A member class of a class template may be defined outside the class template definition in which it is declared. [Note: The member class must be defined before its first use that requires an instantiation (17.8.1) For example,
template<class T> struct A { class B; }; A<int>::B* b1; // OK: requires A to be defined but not A::B template<class T> class A<T>::B { }; A<int>::B b2; // OK: requires A::B to be defined
—end note ]
This should work fine too:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
};
template<typename T>
struct foo_impl<T>::bar{ int value{42}; };
using foo = foo_impl<>;
int main()
{
return foo{}.x.value;
}
I'll answer the third part of your question - as IANALL (not a language lawyer).
The code is invalid for the same reason it's invalid to use a function before it has been declared - even though the compiler can figure out what the function's supposed to be by going further down in the same translation unit. And the cases are similar also in the sense that if you happen to have just a declaration with no definition, that's good enough for the compiler, while here you happen to have a template definition before the instantiation.
So the point is: The language standard mandates that the compiler does not look ahead for you when you want to define something (and a class template is not a definition of a class).
The real answer might be ¯\_(ツ)_/¯, but it's probably currently okay because templates are magical, but it may be more explicitly not okay pending some other core issue resolutions.
First, the main problem of course is [class.mem]/14:
Non-static data members shall not have incomplete types.
This is why your non-template example is ill-formed. However, according to [temp.point]/4:
For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.
Which suggests that foo_impl<void>::bar
is instantiated before foo_impl<void>
, and hence it's complete at the point where the non-static data member of type bar
is instantiated. So maybe it's okay.
However, core language issues 1626 and 2335 deal with not-exactly-the-same-but-still-quite-similar issues regarding completeness and templates, and both point to desiring to make the template case more consistent with the non-template case.
What does all of this mean when viewed as a whole? I'm not sure.
I am not sure that the accepted answer is the correct explanation, but it is the most plausible one for now. Extrapolating from that answer, here are the aswers to my original questions:
template
) considered invalid? If the compiler can figure out the second option, I don't see a reason why it wouldn't be able to figure out the first one. [ Because C++ is weird - it handles class templates differently than classes (you could have probably guessed this one). ]When instantiating foo{}
in main
the compiler instantiates an (implicit) specialization for foo_impl<void>
. This specialization references foo_impl<void>::bar
on line 4 (bar x;
). The context is within a template definition so it depends on a template parameter, and the specialization foo_impl<void>::bar
is obviously not previously instantiated, so all the preconditions for [temp.point]/4 are fulfilled, and the compiler generates the following intermediate (pseudo)code:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$ int value{42};
$ };
// implicit specialization of foo_impl<void>
$ struct foo_impl<void> {
$ struct bar;
$ bar x; // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }
As per [temp.spec]/4:
A specialization is a class, function, or class member that is either instantiated or explicitly specialized.
so the call to foo{}.x.value
in the original implementation with templates qualifies as a specialization (this was something new to me).
The version with explicit specialization does not compile as it seems that:
if the context from which the specialization is referenced depends on a template parameter
no longer holds, so the rule from [temp.point]/4 does not apply.