Search code examples
c++templatestemplate-meta-programming

Why is member function return type instantiated much later than the expression types it depends on?


Pardon the confusing title.

I have this code, which is accepted by GCC, Clang, and MSVC:

#include <type_traits>

template <typename T>
struct Reader
{
    friend auto adl(Reader<T>);
};

template <typename T, typename U>
struct Writer
{
    friend auto adl(Reader<T>) {return U{};}
};

struct A
{
    struct Tag {};
    auto helper() -> decltype(void(Writer<Tag,decltype(this)>{})) {}
    using Self = std::remove_pointer_t<decltype(adl(Reader<Tag>{}))>;
};

It uses stateful metaprogramming to write a type, and then read it back.

Notice the weird use of Writer. I thought I could simplify it to:

auto helper() -> Writer<Tag,decltype(this)> {return {};}

But then it stops working (on all three compilers), due to Writer being instantiated after the read happens. run on gcc.godbolt.org

But if I then move using Self = ... to a static function body below helper, it starts working again. Meaning Writer is now instantiated along with the body of helper, not with its declaration.

After experimenting with different return types, it seems that Writer is instantiated early whenever it's used as a type of an expression. Here's another example: auto helper() -> std::enable_if_t<(Writer<Tag,decltype(this)>{}, true), void> {}

What causes this difference? Since the three compilers agree on this, it's not a fluke of stateful templates, right?


Solution

  • The declaration auto helper() -> Writer<Tag,decltype(this)>; doesn't cause instantiation of Writer<Tag,decltype(this)> because the return type is not required to be complete.

    The definition of helper() and with it the implicit instantiation of Writer<Tag,decltype(this)> are then in a complete-class context, which it seems the compilers assume means that the member function definition is located after the class definition and that the point-of-instantiation of Writer<Tag,decltype(this)> is then also after the class definition.

    I don't know whether this is actually how it is supposed to work or which paragraph in the standard would specify this behavior, though.

    Reading the tag only in the body of another (static) member function causes the read to also be located in a complete class context, i.e. after the class definition in the above interpretation, which is why it works again.