Search code examples
c++templateslanguage-lawyerc++20instantiation

Why is a template parameter or return type of a function declaration not instantiated?


I am trying to figure out why the following code is actually legal (it is, isn't it?):

template <class T>
struct Container {
    static_assert(sizeof(T) == 0); // Not triggered
    T t;
};

struct Foo;

Container<Foo> Get();
void Set(Container<Foo> v);

// Get() and Set() are NEVER defined and used

This compiles without issues. I am of course aware that you can use incomplete types in function declarations as return and parameter types. However, what I am wondering is why the "Container<Foo>" in the Get() and Set() declarations apparently do not cause an instantiation of the Container template? Especially, some kind of template parameter consistency checking is actually performed, since e.g. attempting to return a Container<0> fails to compile.

The question arose because instantiating most standard library components such as std::optional with an incomplete type is undefined behavior. Thus: Is using std::optional<Foo> with an incomplete Foo in function declarations already undefined behavior?

What "parts" of the template do actually get checked by the compiler in this context?
What rules govern this and where in the C++ standard is it handled?


Solution

  • However, what I am wondering is why the "Container" in the Get() and Set() declarations apparently do not cause an instantiation of the Container template?

    A class template specialization is only implicitly instantiated when it is, for the first time in the translation unit, used in a context where the type is required to be complete or completeness would affect the interpretation of the program.

    As you said yourself, whether or not the type in the function declaration is complete doesn't matter, so there won't be any implicit instantiation either.

    attempting to return a Container<0> fails to compile.

    Container<0> is in itself wrong, just by Containers declaration, regardless of its definition. It violates the semantic requirements on the template argument for a template that is specified to take a type as first template parameter. Similarly, if e.g. you added type constraints, these would be checked whenever the specialization is named, not only when it is instantiated.

    The question arose because instantiating most standard library components such as std::optional with an incomplete type is undefined behavior. Thus: Is using std::optional with an incomplete Foo in function declarations already undefined behavior?

    No, using std::optional<Foo> as a return type or function parameter type with an incomplete Foo in a function declaration is allowed. Only if this happens in the definition of the function would the library requirement be violated. (However there are a few special cases, e.g. the return type in a covariant override of a virtual member function must be complete already in its declaration.)

    I am trying to figure out why the following code is actually legal (it is, isn't it?):

    It isn't legal, but not for the reason you think. There is not type T for which sizeof(T) can be evaluated and give 0. Therefore the template itself makes the program ill-formed, no diagnostic required. A compiler is allowed to reject a template that can never be instantiated.

    With C++23 this rule is going to be relaxed to ignore instantiation failures due to static_asserts that assert. So then your program is well-formed. This is also considered a DR against previous C++ versions.