Incomplete Class Usage in Template

Class template inheriting from incomplete class

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.

Use of incomplete types in templates

Clang is correct in reporting an error (as opposed to a warning or being silent about it), though MSVC's and GCC's behavior are also consistent with the standard. See @HolyBlackCat's answer for details on that.

The code you posted is ill-formed NDR. However, what you want to do is feasible.

You can defer the definition of template member functions the same way you would for a non-template class. Much like non-template classes, as long as these definitions requiring bar to be a complete type happen only once bar is complete, everything is fine.

The only hiccup is that you need to explicitly mark the method as inline to avoid ODR violations in multi-TU programs, since the definition will almost certainly be in a header.

#include <iostream>

struct bar;

template <typename T>
struct foo {

foo(bar* b) : b(b) {
}

inline void frobnicate();

T val_;
bar* b;
};

struct bar {
void frobnicate() {
std::cout << "foo\n";
}
};

template <typename T>
void foo<T>::frobnicate() {
b->frobnicate();
}

int main() {
bar b;
foo<int> f(&b);
f.frobnicate();
return 0;
}

Incomplete class usage in template

(I was waiting to Alf Steinbach to post an answer, but since he is not doing it, I will post the reference that he mentioned in the Lounge C++ chat):

The standard indicates that template instantiations are performed after the translation unit has already been translated, so that in time, template instantiations happen after all the non templated elements have already been processed. This is described in section 2.2 Phases of translation:

Paragraphs 1-6 define the preprocessor work and basic textual operations (conversions of the character set, concatenation of literals...)

7/ White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. (2.7). The resulting tokens are syntactically and semantically analyzed and translated as a translation unit.

8/ Translated translation units and instantiation units are combined as follows: Each translated translation unit is examined to produce a list of required instantiations. The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available. All the required instantiations are performed to produce instantiation units. [ Note: These are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. — end note ] The program is ill-formed if any instantiation fails.

I have removed some of the notes for brevity. Now the important bit seems to be that the code is translated without triggering template instantiations in one step, and then in a later step the templates are instantiated. This in turn means that if the type is complete anywhere in the translation unit, it will have been processed by the time the compiler gets to the instantiation.

Disclaimer: This seems like a good reason for all of the compilers that I have tried showing the exact same behavior (gcc, clang, comeau, VS 2010), but this only states when in time the instantiation is performed, it does not explicitly state that the type can be incomplete at the point of instantiation of the template.

incomplete class usage with auto in template class

[dcl.fct.def.general]/2:

The type of a parameter or the return type for a function definition shall not be an incomplete or abstract (possibly cv-qualified) class type in the context of the function definition unless the function is deleted ([dcl.fct.def.delete]).

But [dcl.spec.auto]/10:

Return type deduction for a function template with a placeholder in its declared type occurs when the definition is instantiated even if the function body contains a return statement with a non-type-dependent operand.

So with B, it's ill-formed by the first rule. But with auto, deduction doesn't take place until the function is instantiated... by which point the type is complete, so it's fine.

Note that the first rule only applies to the definition, which is why do_f() is okay. You can have declarations which return incomplete types.


The above wording technically doesn't apply to this case. We don't have a function template. But the intent is for it to apply to any kind of templated thing. There's a PR to editorially fix this from:

Return type deduction for a function template with a placeholder [...]

To:

Return type deduction for a templated entity that is a function or function template with a placeholder in its

Which does apply here.

Template class and 'invalid use of incomplete type' error

You can apply SFINE(i.e."Substitution Failure Is Not An Error") technique along with function overloading to choose the correct method when T is std::complex in Matrix<T> class instatiation.

Following is the demonstration of the idea: (See example code online live)

#include <type_traits>  // std::enable_if, std::false_type

// traits for checking, T is `std::complex`
template<typename> struct is_std_complex : std::false_type {};
template<typename T> struct is_std_complex<std::complex<T>> : std::true_type {};

// traits for `std::enable_if` helpers
template<typename Type, typename ReType = void>
using EnabledForComplex = typename std::enable_if<is_std_complex<Type>::value, ReType>::type;

template<typename Type, typename ReType = void>
using EnabledNotForComplex = typename std::enable_if<!is_std_complex<Type>::value, ReType>::type;

template<typename T>
class Matrix
{
// ...members

public:
template<typename Type = T>
auto is_hermitian() const -> EnabledNotForComplex<Type, bool>
{
// ... code for non-std::complex types
}

template<typename Type = T>
auto is_hermitian() const->EnabledForComplex<Type, bool>
{
// ... code for std::complex types
}
};

That being said, if you have access to c++17, you could use if constexpr, which will only instantiate the branch, which is true for the case at compile time. (See example code online live)

#include <type_traits> // std::false_type

// traits for checking, T is `std::complex`
template<typename> struct is_std_complex : std::false_type {};
template<typename T> struct is_std_complex<std::complex<T>> : std::true_type {};

template<typename T>
class Matrix
{
// ...members

public:
bool is_hermitian() const
{
if (!is_squared()) return false;
if constexpr (is_std_complex<T>::value)
{
// ... code for std::complex types
}
else
{
// ... code for non-std::complex types
}
return true;
}
};

Is instantiating a class template with an incomplete type ill-formed, if the type is defined afterwards?

Assuming we only have one translation unit, [temp.point] rules out your quote as a possible source of ill-formedness

A specialization for a class template has at most one point of instantiation within a translation unit.

Instead, the problem with the first snippet is [temp.expl.spec]

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.

The second snippet is well-formed, there is no requirement that template parameters need to have complete type.

The third snippet is ill-formed, new T requires that T be a complete type. A slight catch here is that the definition of the constructor is implicitly instantiated at Foo<A> foo;. If however, the snippet is changed to

struct A;

template <typename T>
struct Foo {
Foo() {
new T;
}
};

using FooA = Foo<A>;

struct A {};

Then the definition of the constructor isn't instantiated and will therefore be well-formed. [temp.inst]

The implicit instantiation of a class template specialization causes

  • the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and [...]

The fourth snippet is ill-formed because members need to have complete type. [class.mem]

The type of a non-static data member shall not be an incomplete type [...]

c++ template Incomplete type using pointer of class inside template

The placement of the asterisk * in the declaration

GridNode2D<T>* left, right, up, down ;

is misleading. The "standard" C way of declaration would make it clearer:

GridNode2D<T> *left, right, up, down ;

In the above it's more clear that the asterisk belongs to the declaration of left, and that's the problem you have: You only declare left as a pointer, not the other variables.

Since the other variables are not pointers, you need the full definition of GridNode2D<T> to be able to define instances of that class, but that's impossible since the objects are part of the GridNode2D<T> itself. Which leads to the errors you get.

Either use the asterisk on all variables in the declaration, or for better readability split the declarations into multiple lines:

GridNode2D<T>* left;
GridNode2D<T>* right;
GridNode2D<T>* up;
GridNode2D<T>* down;

Incomplete type is not allowed in a class, but is allowed in a class template

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.



Related Topics



Leave a reply



Submit