This code fails to compile because of the inheritance from an incomplete type (https://godbolt.org/z/G35wj9):
template<typename>
class Incomplete;
class T : public Incomplete<T> {};
template<typename>
class Incomplete {};
int main()
{
[[maybe_unused]] T x;
}
I believed that this rule also applied to class templates. However, this code compiles fine (https://godbolt.org/z/cU6GNt):
template<typename>
class Incomplete;
template<int d>
class T : public Incomplete<T<d>> {};
template<typename>
class Incomplete {};
int main()
{
[[maybe_unused]] T<1> x;
}
When class templates are involved, is the base class only required to be complete at the point of instantiation?
When class templates are involved, is the base class only required to be complete at the point of instantiation?
If it's a dependent base then yes. By that virtue a compiler has no idea what Incomplete<T<d>> is at the point of template definition. After all, for some values of d we can have ourselves a specialization of Incomplete<T<d>> that is entirely different from the primary template declaration.
template<>
class Incomplete<T<0>> {};
This is not a circular dependency. Simply naming the specialization T<0> does not cause it to be instantiated. It's just a type name. But it does mean the compiler has no recourse but wait until it can check the base class is valid.
On the other hand, if the base class is not a dependent type, then using it as base would be ill-formed.
The standard covers this in [temp.inst] §1 (taken from C++17) :
Unless a class template specialization has been explicitly instantiated (17.7.2) or explicitly specialized (17.7.3), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. [ Note: In particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and a conversion between pointers to class type depends on the inheritance relationship between the two classes involved. — end note ] [ Example:
template<class T> class B { /* ... */ };
template<class T> class D : public B<T> { /* ... */ };
void f(void*);
void f(B<int>*);
void g(D<int>* p, D<char>* pp, D<double>* ppp) {
f(p); // instantiation of D<int> required: call f(B<int>*)
B<char>* q = pp; // instantiation of D<char> required: convert D<char>* to B<char>*
delete ppp; // instantiation of D<double> required
}
— end example ] If a class template has been declared, but not defined, at the point of instantiation (17.6.4.1), the instantiation yields an incomplete class type (6.9). [ Example:
template<class T> class X;
X<char> ch; // error: incomplete type X<char>
— end example ] [ Note: Within a template declaration, a local class (12.4) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, noexcept-specifiers, and non-static data member initializers, if any). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. — end note ]
Also covered on cppreference.
Just keep in mind that a class template is not a type, and no code is generated for it. Code is generated when the template is instantiated. When a class template is instantiated (implicitly or explicitly), an actual class (type) is generated (including the code for it).
The only difference is, that in your example you use the Incomplete class inside of a template.
In the original example it was used by a class. The type of the class will be created by the compiler at this time, thus before Incomplete is defined.
In your code, Incomplete is used in another template T, (roughly speaking: T is a mold to generate various types by use of Incomplete). At this time the compiler does nothing, it just stores a "rule to generate types" (what I called the mold).
The compiler checks validity of T at the time it is used, hence at the line - then the actual type T<1> : public Incomplete gets generated.
T (the mold) is "valid" iff Incomplete is defined
[[maybe_unused]] T<1> x;
At this point the template class Incomplete is well defined, and the compiler is happy.
Related
#include <iostream>
template<class T> struct A {
enum E : T;
};
template<class T> enum A<T>::E : T { eT };
template<> enum A<char>::E : char { //#1
echar
};
int main(){
}
Consider the above code which is mentioned in temp.expl.spec#6, it's a typically ill-formed code. The rule says:
If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.
At the point #1, where the specialization A<char> will cause implicit instantiation
As a contrast:
#include <iostream>
template<class T> struct A {
struct Test;
};
template<typename T>
struct A<T>::Test{
};
template<>
struct A<int>::Test{ //#2
int c;
};
int main(){
}
Consider this code, At the point #2, where the specialization A<int> also cause implicit instantiation, The difference of the implicit instantiation between such two codes is said as the following:
temp.inst#2
The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. However, for the purpose of determining whether an instantiated redeclaration is valid according to [basic.def.odr] and [class.mem], a declaration that corresponds to a definition in the template is considered to be a definition.
Because in the first code, the member enumeration is unscoped, hence the implicit instantiation of A<char> will also implicit instantiated the definition for enum E. As a contrast, in the second, Test is a member class, hence the implicit instantiation of specialization A<int> will only cause implicit instantiation of the declaration for member class Test rather than definition.
As a variant case of the first code:
#include <iostream>
template<class T> struct A {
enum E : T;
};
template<> enum A<char>::E : char {
echar
};
template<> enum A<int>::E : int { //#1
echar
};
int main(){
}
The difference from the first code is that there's no definition for member enumeration E of its enclosing primary class template, within the primary class template A, it's just a declaration.
So, I think the rule in [temp.expl.spec#6] is not clear. As these above codes shown, in each case, the specialization of primary class template all cause the implicit instantiation for class member, The distinction is one expect the implicit instantiation of declaration , the other is definition.
So, I think modified the rule as the following:
If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation of the definition that instantiated from the definition for the corresponding entity, in every translation unit in which such a use occurs; no diagnostic is required.
It will be conform the observable result. If I miss something, please correct me.
Is it valid to use an identifier of a class, that is only declared but not defined, as template argument and for template specialization.
Something like that:
template<typename T>
class NodeInfo;
template<typename T>
class GraphInfo;
template<typename T>
class Graph {
public:
GraphInfo<T> graphInfo;
NodeInfo<T> nodeInfo;
};
// specialisation
class ContextInfo;
template <>
class NodeInfo<ContextInfo> {
public:
int a, b, c;
};
template <>
class GraphInfo<ContextInfo> {
public:
int a, b, c;
};
int main() {
Graph<ContextInfo> g;
}
This compiles fine without any warning in gcc 7, but I'm wondering if this is a valid thing to do or do I create some kind of undefined behavior with that?
This is fine in principle:
[ Note: A template type argument may be an incomplete type (6.9). — end note ]
However, incomplete class types may not be used with the standard library, unless otherwise specified:
[The] effects are undefined in the following cases: [...]
— if an incomplete type (6.9) is used as a template argument when instantiating a template component,
unless specifically allowed for that component.
For example:
The template parameter T of declval may be an incomplete type.
Since C++17, more library facilities permit the use of incomplete types; for example vector:
An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness
requirements. T shall be complete before any member of the resulting specialization of
vector is referenced.
Yes it's pretty fine. An incomplete type is allowed to used as template argument.
A template argument for a type template parameter must be a type-id, which may name an incomplete type
I just figured out that C++ requires a typename in the following code (see the member function definition at the end of the code fragment):
template <typename T> struct ClassWithSubtype
{
struct Sub
{
//static void check( const ClassWithSubtype<T>::Sub& sub );
ClassWithSubtype<T>::Sub& operator=( const ClassWithSubtype<T>::Sub& other );
};
};
/*
//Here C++ does not require a typename for the argument type
template <typename T> void ClassWithSubtype<T>::Sub::check( const ClassWithSubtype<T>::Sub& sub )
{
//do sth.
}
*/
//Here C++ requires a typename for the return type
template <typename T> typename ClassWithSubtype<T>::Sub& ClassWithSubtype<T>::Sub::operator=( const ClassWithSubtype<T>::Sub& other )
{
//do sth.
}
I can completely understand that C++ requires the keyword typename for the return type. What I do not understand is why NO typename is needed for the argument type of the example function check (which is commented out). Furthermore, why is a typename required in the definition of the assignment operator, but not in its declaration?
These two rules together (both in 14.6.2.1) yield the observed behavior:
First, that
A name refers to the current instantiation if it is
in the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, the injected-class-name (Clause 9) of the class template or nested class,
in the definition of a primary class template or a member of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below)
enclosed in <> (or an equivalent template alias specialization),
in the definition of a nested class of a class template, the name of the nested class referenced as a
member of the current instantiation, or
and one more unrelated bullet point.
Then,
A name is a member of the current instantiation if it is
An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [ Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template. — end note ]
and two unrelated bullet points.
Therefore, in both cases, ClassWithSubType<T> refers to the current instantiation. But as the note explains, Sub refers to a member of the current instantiation only when used inside the class template body. In the out-of-class definition, it becomes a "member of an unknown specialization".
As a member of the current instantiation, the compiler determines that Sub is a class type.
Outside the class body, the compiler doesn't know this and the typename keyword is needed.
Let's consider simple example about template implicit instantiation:
#include <iostream>
template<int N>
class A
{
static const int a = A<N-1>::a; //1, OK, doesn't require implicit intantiation
};
template<int N>
class B
{
static const int a = B<1>::a; //2, Error, implicit instantiation of template 'B<1>' within its own definition
};
int main(){ }
DEMO
The standard wasn't clear about that fact. What it says is only that N3797::14.7.1/1 [temp.inst]:
Unless a class template specialization has been explicitly
instantiated (14.7.2) or explicitly specialized (14.7.3), the class
template specialization is implicitly instantiated when the
specialization is referenced in a context that requires a
completely-defined object type or when the completeness of the class
type affects the semantics of the program.
Neither 1 nor 2 don't require the class type to be completely defined, however the second one causes the error about implicit instantiation. I'd like to understand why.
I think the reason B<1> fails immediately and A<N-1> not is because of temp.res#general-8.4
The validity of a template may be checked prior to any instantiation. The program is ill-formed, no diagnostic required, if:
a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter
This code:
template <class T>
class Foo {};
typedef Foo<void*> Bar;
template <class T>
class Foo<T*> : public Bar {};
// use Foo<int*> somewhere.
Compiles and works fine in MSVC 9.0, but doesn't compile in GCC 4.1.1 or GCC 4.3.4, with the error:
error: invalid use of undefined type 'class Bar'
Is this illegal C++ that MSVC accepts incorrectly, or a limitation of GCC?
Either way, how can I work around this get the desired behaviour: pointer specialisations of Foo that inherit from unspecialised Foo<void*>?
You cannot do that, except by writing the specialization for all T*, except when T is void. Otherwise, you will derive the class from itself, which for obvious reasons can't work.
Instantiating the primary class template for arguments that have an explicit or partial specialization is not possible. If you try to, by provoking an instantiation before the explicit or partial specialization is visible (note that your code did not provoke such an instantiation), your program is ill-formed, with no diagnostic being required (which effectively renders the behavior undefined).
To achieve the above work-around, you can use SFINAE
template <class T>
struct isnt_void { typedef void type; };
template<> struct isnt_void<void> { };
template <class T, class = void>
class Foo {};
template <class T>
class Foo<T*, typename isnt_void<T>::type> : public Foo<void*> {};
The typedef is a red herring.
The following code is equivalent:
template <class T>
class Foo {};
template <class T>
class Foo<T*> : public Foo<void*> {};
It should be clear that, although Foo<T*> is declared at this point, it is not defined. And thus you may not use it as a base.
[class.name] (2003 wording, 9.1/2):
A class definition introduces the class name into the scope where it is defined
[class.mem] (2003 wording, 9.2/2):
A class is considered a
completely-defined object type (3.9)
(or complete type) at the closing } of
the class-specifier. Within the class
member-specification, the class is
regarded as complete within function
bodies, default arguments and
constructor ctor-initializers
(including such things in nested
classes). Otherwise it is regarded as
incomplete within its own class
member-specification.
[class.derived] (2003 wording, 10/1):
The class-name in a base-specifier shall not be an incompletely defined class (clause 9);
A superior solution would be to compose of Foo<void*>. After all, you don't want the raw void* interface cluttering up your stuff, and you don't want a Foo<T*> to be convertible to a Foo<void*>.
Alternatively, you could fully specialize Foo<void*> beforehand.
Assuming, of course, that you're doing this for type folding, instead of because you actually want inheritance.