Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 720
willj Avatar asked Jul 04 '13 22:07

willj


2 Answers

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; 
};
like image 106
Johannes Schaub - litb Avatar answered Jan 23 '23 20:01

Johannes Schaub - litb


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>.

like image 40
dyp Avatar answered Jan 23 '23 21:01

dyp