When instantiating a template, should members of its incomplete argument types be visible?
Asked Answered
D

2

7

In the following example, A has a member typedef Instantiate which causes the instantiation of B<A>.

template<typename T>
struct B
{
    typedef typename T::Before Before; // ok
    typedef typename T::After After; // error: no type named 'After' in 'A<int>'
};

template<typename T>
struct A
{
    typedef int Before;
    typedef typename B<A>::After Instantiate;
    typedef int After;
};

template struct A<int>; // instantiate A<int>

All the compilers I've tried report that, while A::Before is visible, A::After is not. Is this behaviour compliant with the standard? If so, where does the standard specify which names in A should be visible during instantiation of B<A>?

If dependent names are "looked up at the point of the template instantiation", what does this mean in the scenario of a name qualified by a template parameter such as T::After?

EDIT: Note that the same behaviour occurs when A is not a template:

template<typename T>
struct B
{
    typedef typename T::Before Before; // ok
    typedef typename T::After After; // error: no type named 'After' in 'A'
};

struct A
{
    typedef int Before;
    typedef B<A>::After Instantiate;
    typedef int After;
};

.. and G++ accepts the following, but Clang does not:

template<typename T>
struct B
{
    static const int value = 0;
    static const int i = T::value; // clang error: not a constant expression
};

struct A
{
    static const int value = B<A>::value;
};

EDIT: After some reading of the C++03 standard:

[temp.dep.type] A type is dependent if it is a template parameter

Therefore T is dependent.

[temp.res] When looking for the declaration of a name used in a template definition, the usual lookup rules are used for nondependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known.

The lookup of T::After is therefore postponed until the argument for T is known.

[temp.inst] Unless a class template specialization has been explicitly instantiated ... the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type.

Therefore the declaration of A<int>::Instantiate requires the instantiation of B<A> (because it is used in a nested-name-specifier.)

A<int>::After is not visible at the point of declaration of A<int>::Instantiate, so the behaviour of the compiler makes sense - but I haven't seen anything in C++03 that explicitly describes this behaviour. The closest thing was this somewhat vague paragraph:

[temp.dep.res] In resolving dependent names, names from the following sources are considered:

— Declarations that are visible at the point of definition of the template.

Demagogic answered 4/7, 2013 at 22:2 Comment(10)
I'd say those are dependent names [temp.dep], and they're looked up at the point of the template's instantiation [temp.dep]/1 -> [temp.point]Bakehouse
I read that paragraph a hundred times, but only now does it make sense..Demagogic
@DyP You should make that an answer ;)Saleable
@DyP: I agree with Captain Obvlious.Fanchon
Hm, no, I think there's still something missing in the name lookup of incomplete types; but I might be wrong.Bakehouse
edited to simplify the example, without breaking DyP's answer ;)Demagogic
@Demagogic Huh? It might be different if A is a template. I'm still a bit confused about [temp.point]/4. Better leave it with the template, the answer might be different. I might as well change my exampleBakehouse
Is your question about C++03 or C++11 (i.e. the current Standard)?Bakehouse
Both - I'm assuming that C++11 has not changed the correct behaviour, but I'm interested in language in any version of the standard that defines what is correct.Demagogic
I think the last quote, [temp.dep.res], is not important in this case, as IMO the name is T and this name has already been declared (as a template parameter) when used to define e.g. B<A>::before. It doesn't care for name lookup that T refers to A, as far as I understand it.Bakehouse
I
4

Whether typename T::Before is valid is not explicitly said by the spec. It is subject of a defect report (because the Standard can very reasonably be read to forbid it): http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#287 .

Whether typename T::After is invalid can also very reasonably be read to be true by the spec, and actually it makes quite a bit of sense (and aforementioned DR still keeps it ill-formed). Because you have an instantiation of a class A<Foo>, which references another class A<Bar> during a period where a member Baz has not yet been declared, and that makes a reference back to A<Foo>::Bar. That is ill-formed in the case of non-templates aswell (try to "forget" for a moment that you are dealing with templates: surely the lookup of B<A>::After is done after the A template was completely parsed, but not after the specific instantiation of it was completely created. And it is the instantiation of it that actually will do the reference!).

struct A {
   typedef int Foo;
   typedef A::Foo Bar; // valid
   typedef A::Baz Lulz; // *not* valid
   typedef int Baz; 
};
Inmost answered 5/7, 2013 at 22:58 Comment(3)
"The consensus was that the template case should be treated the same as the non-template class case in terms of the order in which members get declared/defined and classes get completed." I guess that defines the 'compliant' behaviour ;)Demagogic
This is a defect report from 2001 and they still haven't resolved it? Or am I just too confused?Bakehouse
@DyP there are multiple DRs 10+ years old :)Inmost
B
2

T::Before and T::After are dependent names due to [temp.dep.type]/8 and /5.

Dependent names are looked up "at the point of the template instantiation (14.6.4.1) in both the context of the template definition and the context of the point of instantiation." [temp.dep]/1

I interpret this as: They're looked up when the template is instantiated. Where are they looked up? At the context of the template definition and the context of the point of instantiation.

[temp.dep.type]/7 On the other hand states:

If, for a given set of template arguments, a specialization of a template is instantiated that refers to a member of the current instantiation with a qualified-id or class member access expression, the name in the qualified-id or class member access expression is looked up in the template instantiation context.

[temp.point]/7 Defines the instantiation context as follows:

The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.

Therefore, we need to know what the point of instantiation is.

[temp.point]/4

For a class template specialization [...], if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template.

Although the injected class name A is arguably a context that depends (as a layman term) on the template parameters of A, the name A itself is not a dependent name. Correction by Johannes Schaub: It is a dependent name. See [temp.local]/1 and [temp.dep.type]/8 => A is a dependent type.

Therefore, this condition is not fulfilled, and B<A> should be instantiated before A<int>.

Bakehouse answered 4/7, 2013 at 23:7 Comment(8)
hmm.. if B<A> changes to B<A, T>, does that change the point of instantiation of B?Demagogic
I'm pretty sure the name lookup behaviour is due to the 'completeness' of A, and not the point of instantiation.Demagogic
@Demagogic I found a special rule for name lookup that applies here; yet the point of instantiation still plays an important role.Bakehouse
OK, just one thing: "The instantiation context of an expression..." there are no 'expressions' in the example, using the strict C++ definition of the word.Demagogic
There are id-expression s in the example.Bakehouse
Some unresolved issues with the answer: typedef declarations do not have external linkage. B<A> is a dependent type (the 'typename' is required).Demagogic
"the name A itself is not a dependent name.". This is incorrect. "A" is a dependent name (but note that you are still allowed to say both "A::Before" and "A::After" in C++11 without typename).Inmost
@JohannesSchaub-litb I thought A was not dependent because it refers to the current instantiation? It shouldn't name be a dependent type, but I can't find what the Standard says about A being a dependent name.Bakehouse

© 2022 - 2024 — McMap. All rights reserved.