0

Problem

Consider this code (live version), where I have forward-declared a struct S before a function template Use which ODR-uses it. S is defined immediately after the function, but before any instantiation of Use.

struct S;

template<typename T> void Use(S& s, const T& t) { // ODR-use S
    s.Use(t); // error: member access into incomplete type 'S'
}

struct S {
    template<typename T> void Use(const T&) { }
};

int main() {
    auto s = S{}; Use(s, 2); // Instantiation of Use<...>
}

This does not compile, because apparently, the definition of S is somehow invisible to the Use function.

Rationale

Based on my understanding of function templates after reading §13.9.1 of the latest working draft this shouldn't be a problem, as all types are fully defined when the function template is instantiated.

Although, to me it seems like all dependent types will have access to the definitions available at instantiation, while non-dependent types will use the version available at the function definition. Unfortunately I wasn't able to find the corresponding paragraph in the standard which defines this behavior.

Which part of the standard have I missed to explain this behavior?

Work Around

To work around the issue, I can "refresh" the current definition by using IIFE:

// This is OK:
template<typename T> void Use(S& s, const T& t) {
    [&](auto& s_) { s_.Use(t); }(s);
}

Or alternatively by making a dependent type alias for S:

// This is also OK:
template<typename T, typename SS = S> void Use(SS& s, const T& t) {
    s.Use(t);
}

So now I'm wondering, why doesn't an instantiated template always have access to the latest definition of any type, instead of only the dependent types? As I've shown above, you can force the compiler to update any type by using an immediately evaluated lambda - so why isn't this done automatically?

nyronium
  • 1,258
  • 2
  • 11
  • 25
  • 2
    Related, if not a duplicate of https://stackoverflow.com/questions/12561544/two-phase-name-lookup-for-c-templates-why and https://stackoverflow.com/questions/7767626/ – StoryTeller - Unslander Monica Nov 03 '19 at 14:11
  • @StoryTeller-UnslanderMonica While related, both threads unfortunately don't answer my question. This question is more about the specific case I have shown, not the overall rationale for two-phase lookup. I want to know why I have to write this ugly workaround, when the compiler could figure it out itself. – nyronium Nov 03 '19 at 14:21
  • 1
    But that's the point. It can't figure it out. Two phase lookup demands it. – StoryTeller - Unslander Monica Nov 03 '19 at 14:22
  • @StoryTeller-UnslanderMonica Well it can, as i have shown with the IIFE example. It could simply use the available definition. And i know it is available, since it works when binding to the lambda. Or do you mean it is not allowed to? – nyronium Nov 03 '19 at 14:22
  • 1
    I guess the rationale is that otherwise accessing member types would always require `typename` keyword, member templates - `template`, not just for dependent types. – smitsyn Nov 03 '19 at 14:24
  • Your solution is shifting the lookup to the second phase of an auxiliary template. You erase S to get it to work. It's not working around two phase lookup, it's working with it. – StoryTeller - Unslander Monica Nov 03 '19 at 14:25
  • 1
    `s.Use` is not a dependent name, therefore only lookup in the definition of the template occurs, in which no function `Use` can be found in `S` . In addition your template itself is ill-formed prior to instantiation (and can be diagnosed, but no diagnostic required for this), because "a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter," – Johannes Schaub - litb Nov 03 '19 at 14:28
  • @nyronium I'm not sure I understand your question. Are you asking for citations showing that the standard mandates the look up you get or for the rationale underlying the standard requirements? – AProgrammer Nov 03 '19 at 14:29
  • @StoryTeller I see, thanks. What is the benefit of evaluating non dependent types in the first lookup phase instead of the second? It would eliminate at least my issue. – nyronium Nov 03 '19 at 14:30
  • @AProgrammer A little of both, but I already have been pointed to the two-phase lookup, which seems to be the reason (but isn't mentioned in the standard about implicit template instanciation). What I still want to know is what the benefit is for doing non-dependent type lookup in the first instead of the second phase. Currently I can only see the disadvantage that my code will not work without the workaround, but I suspect there is a rationale. – nyronium Nov 03 '19 at 14:32
  • 1
    See James Kanze's answer in one of the question pointed by @StoryTeller-UnslanderMonica. Short story, what you want was deemed too errorprone. – AProgrammer Nov 03 '19 at 14:34
  • @AProgrammer James' answer more or less says that there is no rationale. Given that we are close to `c++20` I felt like this answer (from 2012) could have been outdated by now. If there truly is no rationale, I think it should be changed. – nyronium Nov 03 '19 at 14:37
  • Your compiler can *not* figure it out, because it does *not know what you mean*. Suppose you had written `f(s);` and since `s` is non-dependent, believed it always calls the `f` that you defined before your template. How can the compiler know that this time, you don't expect it to take a better matching `f` from a random user who instantiates your template? In general C++ templates strive to be similar to normal non-template functions for normal non-templated types. Therefore they resolve names at the point that you write them. – Johannes Schaub - litb Nov 03 '19 at 14:51
  • @nyronium First phase lookup does a lot of syntax checking, which helps with finding bugs. In order to do that compiler needs to know whether name `Use` refers to member type or data or function or tempate. For non-dependent types, compiler tries to infer that from type definition. For dependent types you write keywords `typename` and `template`. The language design could be that in order to have all the checks, you'd need need to always use `typename` and `template`. – smitsyn Nov 03 '19 at 15:02
  • @nyronium, it seems to me that James gives a rationale for the change: the situation was deemed too error-prone; and James was part of the committee at the time so unless other then-members give their opinion, that's the best rationale we can have. There are at least two reasons not to revert back: the current situation allows far more deeper checks without needing an instantiation (g++ for instance gives such errors), and obviously we have now 20 years or so of code which depend on two-phase look-up. – AProgrammer Nov 03 '19 at 15:08

1 Answers1

1

A forward declaration is just a promise to the compiler that "yes, I promise that this type really will exist at link time". It doesn't provide the declaration of the type. So, it's good enough for return values, pointers or references to the type, but if you actually want to call functions or access member variables, then you need the full declaration and a forward declaration is no longer good enough - hence your compilation error.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • As I have already been pointed to the two-phase lookup, I am now aware of why I get an compilation error. What I am still missing is why it is *strictly necessary* that I do get an error. I would like to know what paragraph in the standard "prohibits" me from writing a proposal to change it. – nyronium Nov 03 '19 at 14:38
  • @nyronium That's a *new* / *different* question. – Jesper Juhl Nov 03 '19 at 14:42
  • 1
    Maybe I have been to unclear, but the last sentence from my original post intended to ask about the strict necessity: *"[...] so why isn't this done automatically?"* – nyronium Nov 03 '19 at 14:49