Home > Software design >  Use of incomplete types in templates
Use of incomplete types in templates

Time:10-26

Is it legal to use an incomplete type in a template if the type is complete when the template is instantiated?

As below

#include <iostream>

struct bar;

template <typename T>
struct foo {

    foo(bar* b) : b(b) {
    }
    
     void frobnicate() {
          b->frobnicate();
     }

     T val_;
     bar* b;
};

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

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

Visual Studio compiles the above without complaining. GCC issues the warning invalid use of incomplete type 'struct bar' but compiles. Clang errors out with member access into incomplete type 'bar'.

CodePudding user response:

The code is ill-formed, no diagnostic required.

[temp.res.general]/6.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, ...


If you absolutely can't define bar before the template, there is a workaround: you can introduce an artifical dependency on the template parameter.

template <typename T, typename, typename...>
struct dependent_type {using type = T;};
template <typename T, typename P0, typename ...P>
using dependent_type_t = typename dependent_type<T, P0, P...>::type;

Then use dependent_type_t<bar, T> instead of bar.

CodePudding user response:

Clang is correct in causing 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;
}
  • Related