Is a specialization implicitly instantiated if it has already been implicitly instantiated?
Asked Answered
R

1

10

The question in the title is clear enough. To be more specific, consider the following example:

#include <type_traits>

template <typename T>
struct is_complete_helper {
    template <typename U>
    static auto test(U*)  -> std::integral_constant<bool, sizeof(U) == sizeof(U)>;
    static auto test(...) -> std::false_type;
    using type = decltype(test((T*)0));
};

template <typename T>
struct is_complete : is_complete_helper<T>::type {};

// The above is an implementation of is_complete from https://mcmap.net/q/25289/-using-sfinae-to-check-if-the-type-is-complete-or-not-duplicate

template<class T> class X;

static_assert(!is_complete<X<char>>::type{}); 
// X<char> should be implicitly instantiated here, an incomplete type

template<class T> class X {};

static_assert(!is_complete<X<char>>::type{}); // #1

X<char> ch; // #2

This code compiles with GCC and Clang.

According to [temp.inst]/1:

Unless a class template specialization has been explicitly instantiated or explicitly specialized, 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.

X<char> is implicitly instantiated due to static_assert(!is_complete<X<char>>::type{}), which generates an incomplete type.

Then, after the definition of X, #1 suggests that X<char> is not instantiated again (still incomplete) while #2 suggests that X<char> is indeed instantiated again (becomes a complete type).

Is a specialization implicitly instantiated if it has already been implicitly instantiated? Why is there a difference between #1 and #2?

An interpretation from the standard is welcome.

Rusticus answered 6/9, 2018 at 9:39 Comment(7)
IIRC, this breaks the odr.Baddie
@Baddie Do you mean #1 and #2 break the odr? But it is so common that a specialization is referred to multiple times...Rusticus
I was wrong, it would have break the odr if multiple TU were involed, as per [temp.point]/8.Baddie
I think the program is ill formed NDR as is_complete<X<char>>::type depends of instantiation point (including EOF one).Cassandra
@Cassandra - The EOF one is only for functions. Classes have only a single POI.Dunaway
Hopefully, nobody will have the idea to define is_complete in a header file !!Caitiff
I think temp.res#8.5 applies.Cassandra
D
4

Is a specialization implicitly instantiated if it has already been implicitly instantiated?

No. According to [temp.point]/8:

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

x<char> need only be instantiated once, and it's not when it's named in the first static assertion, only before ch. But, [temp.point]/8 also says

A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

And is_complete_helper::test is a member function template whose declaration is instantiated before the static assertion. So it must also have an instantiation at the end of the TU. Where it will likely give a different result. So this trait is depending on an ill-formed NDR construct.

Dunaway answered 6/9, 2018 at 9:56 Comment(20)
So is it a bug that #2 compiles?Rusticus
@Rusticus - The bug is in assuming is_complete can report different things.Dunaway
Oh I see the problem of is_complete. But why does #2 compile? Isn't X<char> always incomplete according to your interpretation?Rusticus
@Rusticus - I see what you mean to ask. Just a sec.Dunaway
@Rusticus - Cleaned up the mess that was my ramble. Is it clear now?Dunaway
I have emphasized "... or when the completeness of the class type affects the semantics of the program". Doesn't the completeness of X<char> affect the semantics of the program?Rusticus
@Cassandra - That applies to the template definition itself. Are you saying is_complete itself is ill-formed NDR?Dunaway
@Rusticus - No. The semantics of this single TU is to report false. It doesn't contradict the fact classes can be completed. It just means is_complete, as written cannot report the most up to date info, because it can only be instantiated once for any type in a single TU.Dunaway
I think so, in one case test(X*) doesn't "exists" in one place, and does in the other place.Cassandra
@StoryTeller I mean in the first static_assert, if X<char> is complete, the static_assert fails, and if X<char> is incomplete, the static_assert does not fail, so I conclude that the completeness of X<char> affects the semantics of the program, thus X<char> need to be implicitly instantiated in the first static_assert. What's wrong with this inference?Rusticus
@Rusticus - The assumption that is_complete must adjust its report based on the completeness of its argument in a single TU. It doesn't. It has a single well-defined point of instantiation. It can only report what it was instantiated with there.Dunaway
@Cassandra - The hypothetical instantiation of is_complete immediately after its definition cannot be with X<char> (there is no X declared there). There really is no issue with the test, other than the fact it doesn't update itself when a type is completed in a TU. But that's intentional.Dunaway
@Rusticus - You must also remember that there is no looking into the future to know if the type will be instantiated later in the TU. There is one well-defined instantiation point for is_complete<X<char>>, and that is before the static assertion. After which, it will not be instantiated again. I can see why this is unexpected (given the template's suggestive name), but I don't believe it's UB.Dunaway
@StoryTeller So doesn't the completeness of X<char> there affect the semantics of the program?Rusticus
@Rusticus - No, because is_complete<X<char>> along with all of its members is only instantiated once, and never again in this TU.Dunaway
@StoryTeller Sorry I still don't get your point... As I said, "if X<char> is complete, the static_assert fails, and if X<char> is incomplete, the static_assert does not fail, so I conclude that the completeness of X<char> affects the semantics of the program", all I said is about the first instantiation of is_complete<X<char>>, I never mean is_complete<X<char>> will be instantiated multiple times...Rusticus
@Rusticus - Let's flip this around a bit. Is my last example ill-formed?Dunaway
Let us continue this discussion in chat.Rusticus
Wait... is is_complete_helper::test really instantiated since the substitution of types results in a deduction failure?Rusticus
@Rusticus - There has to be an attempt to instantiate that declaration (temp.inst/8) for substitution to fail. Though you raise a good point. Does it count for the purposes of [temp.point]/8? I think it should, but I find nothing conclusive.Dunaway

© 2022 - 2024 — McMap. All rights reserved.