llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.8k stars 11.45k forks source link

Unexpected template instantiation with extern template declaration #97888

Open kamrann opened 2 months ago

kamrann commented 2 months ago

The following is rejected by clang (and appears to have always been so). GCC accepts it.

template < typename T >
class Foo
{
    static constexpr auto s = sizeof(T);
};

class X;

// Why does this trigger immediate instantiation?
extern template class Foo< X >;

class X
{};

template class Foo< X >;

int main () {
    Foo< X > foo;
}
<source>:5:31: error: invalid application of 'sizeof' to an incomplete type 'X'
    5 |     static constexpr auto s = sizeof(T);
      |                               ^~~~~~~~~
<source>:10:23: note: in instantiation of template class 'Foo<X>' requested here
   10 | extern template class Foo< X >;

Compiler explorer: https://godbolt.org/z/qTssnz68s

Why would the extern template line, which is an explicit instantiation declaration only, trigger the instantiation? Isn't the whole point of such a declaration to prevent unwanted instantiations?

llvmbot commented 2 months ago

@llvm/issue-subscribers-clang-frontend

Author: Cameron Angus (kamrann)

The following is rejected by clang (and appears to have always been so). GCC accepts it. ``` template < typename T > class Foo { static constexpr auto s = sizeof(T); }; class X; // Why does this trigger immediate instantiation? extern template class Foo< X >; class X {}; template class Foo< X >; int main () { Foo< X > foo; } ``` ``` <source>:5:31: error: invalid application of 'sizeof' to an incomplete type 'X' 5 | static constexpr auto s = sizeof(T); | ^~~~~~~~~ <source>:10:23: note: in instantiation of template class 'Foo<X>' requested here 10 | extern template class Foo< X >; ``` Compiler explorer: https://godbolt.org/z/qTssnz68s Why would the `extern template` line, which is an explicit instantiation _declaration_ only, trigger the instantiation? Isn't the whole point of such a declaration to prevent unwanted instantiations?
shafik commented 2 months ago

So I think clang is correct here, it is interesting to note that clang accepts this if we change the member s to not have an deduced type: https://godbolt.org/z/fEfP4b1rb

although edg/MSVC do not accept in this case. gcc is the only one to accept in either case.

We used to have: https://timsong-cpp.github.io/cppwp/n4140/temp.explicit#10

which I believe was changed by: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1815r2

and I think the wording to consider here is now: https://eel.is/c++draft/temp.inst#1

but maybe I am off.

CC @zygoloid @AaronBallman @hubert-reinterpretcast @cor3ntin

kamrann commented 2 months ago

Ok, yep the more I think about it the more I suspect there is something off in my understanding of what exactly extern template is supposed to do, specifically in the case of class templates. And/or there is perhaps an element of the terminology being somewhat inconsistent or unclear.

it is interesting to note that clang accepts this if we change the member s to not have an deduced type

Hmm, perhaps I oversimplified things when reducing the repro then since in my original case it was not triggered by a static member variable like above, but a more complex case with a class member function. But I think it was inline, so perhaps it's the same rule that applies.

My confusion stems from a lot of the literature stating that extern template can be used to reduce compilation time by preventing the compiler from instantiating a template in all translation units. It seems either this is plain incorrect, or it comes down to what exactly is meant by 'instantiating'. Am I right in now suspecting that for class templates, there is a minimal level of instantiation that must always be done regardless, and extern template is only suppressing things further on in the compilation process relating to emission of symbols and code generation?

hubert-reinterpretcast commented 1 month ago

Clang is probably not wrong, but for convoluted or tenuous reasons: https://eel.is/c++draft/temp.explicit#9 says that the explicit instantiation declaration for the class specialization is also an explicit instantiation declaration of each of its direct non-template members. Applying this in the straightforward manner requires the class to be complete.

Note: Explicit instantiation declarations do not only affect implicit instantiaton: they also affect whether an explicit specialization can be declared later; see https://eel.is/c++draft/temp.spec#general-5.