Two Phase Name Lookup for C++ Templates - Why

Two phase name lookup for C++ templates - Why?

They could. This is the way most early implementations of templates
worked, and is still the way the Microsoft compiler worked. It was felt
(in the committee) that this was too error prone; it made it too easy to
accidentally hijack a name, with the instantiation in one translation
unit picking up a local name, rather than the desired global symbol. (A
typical translation unit will consist of a sequence of #includes,
declaring the names that everyone should see, followed by implementation
code. At the point of instantiation, everything preceding the point of
instantation is visible, including implementation code.)

The final decision was to classify the symbols in a template into two
categories: dependent and non-dependent, and to insist that the
non-dependent symbols be resolved at the point of definition of the
template, to reduce the risk of them accidentally being bound to some
local implementation symbols. Coupled with the requirement to specify
typename and template when appropriate for dependent symbols, this
also allows parsing and some error checking at the point of definition
of the template, rather than only when the template is instantiated.

Two phase lookup - explanation needed

Templates are compiled (at least) twice:

  1. Without Instantiation the template code itself is checked for syntax.

    Eg: Any syntax errors such as ; etc.

  2. At the time of instantiation(when the exact type is known), the template code is checked again to ensure all calls are valid for that particular type.

    Eg: The template might in turn call to functions which might not be present for that particular type.

This is called as Two Phase Lookup.

Why do I have warning C4199 two-phase name lookup is not supported for C++/CLI, C++/CX, or openmp?

Ok Thanks to Simon and Raymond's article I was able to solve it. I no more have that warning anywhere in my code. Just added the command as the article describes at it's very bottom.

Here's an image of where exactly I added the command for those like me that might have trouble finding it.

MSVC twoPhase option

C++ two-phase lookup for dependent unqualified name of member function

I found a statement in Standard
3.4.2:

Let X be the lookup set produced by unqualified lookup and let Y be
the lookup set produced by argument dependent lookup. If X contains:

  • a declaration of class member, or

  • a block-scope function declaration that is not a using-declaration, or

  • a declaration that is neither a function or a function template

then Y is empty.

where's the relevant rule for phase two name lookup of template instantiation in the current standard

In other words, these rules mean that only ADL is used to perform in phase two lookup.

This is the wrong conclusion to draw from the given specification quotes. You seem to be confusing two different concepts.

The first concept is what is commonly called "two-phase lookup": the fact that when a template gets parsed, names which are dependent on template parameters cannot be looked up at parse time. They can only be lookup up at instantiation time. There are two phases of lookup: one for non-dependent names, and one for dependent names.

The second concept is just name lookup, which has two scopes of names to search: regular unqualified lookup (defined in [basic.lookup.unqual]) and argument-dependent lookup (defined in [basic.lookup.argdep]). Unqualified lookup searches in the scope of the code itself, while ADL searches in the scope of the namespace of a function's arguments.

Both phases of template name lookup use both of these scopes. To be clear, dependent name lookup also does unqualified lookup. This is why one of the bullet points you quoted said "for the part of the lookup using unqualified name lookup... ". If dependent name lookup didn't use the rules for unqualified name lookup, it wouldn't have mentioned them.

Now, dependent name lookup does restrict what the candidate names are compared to the regular rules. But it doesn't restrict it to just ADL; it does both scopes of lookup.

These restrictions used to be specified in [temp.dep.candidate]/1, as you pointed out. But they got moved around a bit.

For the unqualified lookup part, the new [temp.dep.candidate]/1 covers that as you quoted it, because it says "usual lookup rules from the template definition context". This specifies the context of the unqualified lookup as being the template's definition only.

Note that the second bullet point also says to look in that scope, so it covers part of that.

The rest of the second bullet point is covered by [basic.lookup.argdep]/4.5:

If the lookup is for a dependent name ([temp.dep], [temp.dep.candidate]), any declaration D in N is visible if D would be visible to qualified name lookup ([namespace.qual]) at any point in the instantiation context ([module.context]) of the lookup, unless D is declared in another translation unit, attached to the global module, and is either discarded ([module.global.frag]) or has internal linkage.

This expands the lookup to include the "instantiation context", which largely matches the C++17 text (though with module-based modifications).

Name lookup in template base: why do we add this-

But the second phase lookup invokes only ADL, which does not consider member functions, does it?

It does not. However ADL adds to the lookup set, it does not comprise all of it. Also, the idea you are paraphrasing here applies to a postfix-expression in postfix-expression(args), when said postfix-expression is an unqualified-id.

[temp.dep]

1 ... In an expression of the form:

postfix-expression ( expression-listopt )

where the postfix-expression is an unqualified-id, the unqualified-id
denotes a dependent name if

  • [... specific conditions ...]

If an operand of an operator is a type-dependent expression, the
operator also denotes a dependent name. Such names are unbound and are
looked up at the point of the template instantiation ([temp.point]) in
both the context of the template definition and the context of the
point of instantiation.

So if you had there foo() instead, lookup would not consider members, and would instead try only free functions, both at the point of definition and instantiation (where ADL can add to the lookup set, assuming we had a dependent expression).

But for this->foo (I omitted the call intentionally, to discuss the postfix-expression), we have class member access. And here other paragraphs apply:

[temp.dep.type]

5 A name is a member of the current instantiation if it is

  • [...]
  • An id-expression denoting the member in a class member access expression for which the type of the object expression is the current instantiation, and the id-expression, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [ Note: If no such member is found, and the current instantiation has any dependent base classes, then the id-expression is a member of an unknown specialization; see below.  — end note ]

6 A name is a member of an unknown specialization if it is

  • [...]
  • An id-expression denoting the member in a class member access expression in which either

    • the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name
      lookup of the id-expression does not find a member of a class that is
      the current instantiation or a non-dependent base class thereof; or

7 Similarly, if the id-expression in a class member access expression for which the type of the object expression is the current instantiation does not refer to a member of the current instantiation or a member of an unknown specialization, the program is ill-formed even if the template containing the member access expression is not instantiated; no diagnostic required.

These bullets tell us what lookup to perform when this->foo is encountered. It looks up members only. In our case, we have a non-dependent base class in the current instantiation, so that's where the member is to be found, unambiguously.

Having found the member function, the postfix-expression this->foo denotes a callable, and that is how the function call is resolved.

Two-phase name lookup: PODs vs. custom types

Since T is a template parameter, the expression t is type-dependent and hence foo is a dependent name in the function call foo(t). [temp.dep.candidate] 14.6.4.2/1 says:

For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:

  • For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.

  • For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.


If the function name is an unqualified-id and the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

When instantiating S<double>::foobar, T is obviously double and has no associated namespaces. So the only declarations of foo that will be found are those from the template definition context (void foo(int)) as described in the first bullet.

When instantiating S<A>::foobar, T is A. Declarations of foo from both the definition context

  • void foo(int)

    and from A's associated namespace (the global namespace) are found:

  • inline void foo( A ) { std::cout << "foo(A)" << std::endl; }

  • inline void foo( double ) { std::cout << "foo(double)" << std::endl; }

Clearly void foo(A) is the best match.



Related Topics



Leave a reply



Submit