In clang, why does this template default parameter require the destructor to be instantiated?

Viewed 312
struct incomplete_type;

#if 0
struct incomplete_type {
    static void foo() {}
};
#endif

template<typename T>
struct problem_type
{
    constexpr problem_type(int) noexcept {};
    constexpr problem_type(double) noexcept : problem_type(5) {}

    ~problem_type() {
        T::foo();
    }
};

void bar(problem_type<incomplete_type> arg=5.0) noexcept;

When bar has a default parameter which calls a forwarding constructor which is also constexpr, the compiler also attempts to instantiate the destructor, which fails, because T::foo cannot be called because T is an incomplete type.

The problem does not occur if the default parameter does not invoke a forwarding constructor (e.g. if we change 5.0 to 5), if the forwarding constructor is not constexpr, or (of course) if the type T is complete at this point.

The problem does also occur if the destructor is constexpr, even if the constructor is not forwarding.

noexcept appears to be irrelevant, but I have left it in to ensure the compiler is not attempting to generate stack unwinding code.

This only occurs on clang (12.0.1), not on gcc (11.2) or Visual Studio (19.29). See godbolt

Note that the function bar is not defined or called.

Why does this not compile in clang?

1 Answers

First please note, that Clang is more consistent here, because both GCC and Clang reject a similar example with default initialization of a class field, where destructor body still refers to incomplete type:

struct incomplete_type;

template<typename T>
struct problem_type
{
    constexpr problem_type(int) noexcept {};
    constexpr problem_type(double) noexcept : problem_type(5) {}

    constexpr ~problem_type() {
        T::foo();
    }
};

// ok in all compilers
struct A {
    problem_type<incomplete_type> p;
};

// error in GCC and Clang
struct B {
    problem_type<incomplete_type> p = 5.0;
};

Demo: https://gcc.godbolt.org/z/59hW3ecex

In your example, the destructor of problem_type<incomplete_type> is invoked during the function call, so the compiler must check its existing definition. It looks like only Clang does it properly.

To make your example accepted by all compilers, please just define destructor ~problem_type outside of class body after the function bar:

struct incomplete_type;

template<typename T>
struct problem_type
{
    constexpr problem_type(int) noexcept {};
    constexpr problem_type(double) noexcept : problem_type(5) {}
    constexpr ~problem_type();
};

void bar(problem_type<incomplete_type> arg=5.0) noexcept;

template<typename T>
constexpr problem_type<T>::~problem_type() {
    T::foo();
}

Demo: https://gcc.godbolt.org/z/rvzMbbz9d

But you still need incomplete_type become complete at the place where you call bar function.

Related