4

Consider the following:

struct Base {
    void foo();
};

template <class T>
struct Derived : Base {
    void bar() {
        this->foo();
    }
};

Typically, we explain that this-> makes foo() a dependent name, so its lookup is posponed to the second phase, i.e. to the template instantiation point.

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

I'd appreciate any pointer to the Standard paragraph that explains why the above code compiles.

Igor R.
  • 14,716
  • 2
  • 49
  • 83

2 Answers2

8

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
2

The phrase “two-phase lookup” is widely misunderstood, even among experts. C++17 said both

Such names are unbound and are looked up at the point of the template instantiation (17.6.4.1) in both the context of the template definition and the context of the point of instantiation.

(in [temp.dep]/1) and

In resolving dependent names, names from the following sources are considered:
— Declarations that are visible at the point of definition of the template.
— Declarations from namespaces associated with the types of the function arguments both from the instantiation context (17.6.4.1) and from the definition context.

(in [temp.dep.res]/1), each of which seems to suggest that these are the two phases. However, a better way of interpreting this (unofficial) phrase is that lookup is done

  1. from the template definition: for non-dependent names
  2. during instantiation: from the definition but also, via ADL only, from the instantiation context

The former can be done when parsing the template definition, but no diagnostic is required for uninstantiable templates ([temp.res]/8). Note that the latter includes more than ADL.

The relevant wording has been clarified recently; it now merely says

[Note: For the part of the lookup using associated namespaces ([basic.lookup.argdep]), function declarations found in the template instantiation context are found by this lookup, as described in [basic.lookup.argdep]. — end note]

as a reminder of the difference in the second phase. Nonetheless, it confusingly uses dependent name only to refer to unqualified dependent names/operators ([temp.dep]/2). Names like Derived::foo (from the this->) are clearly dependent; as qualified names, they’re not subject to ADL, but they are still looked up during instantiation—which allows results from a dependent base to be found. (Your example has a non-dependent base, but that’s available then too.)

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • "_Names like Derived::foo (from the this->) are clearly dependent;_" but are the current instantiation. Oh my. This is so complicated. – curiousguy Nov 11 '19 at 21:29
  • 1
    @curiousguy: It’s true that an example for an unknown specialization would be more apt, but I was just going for the general idea of qualified dependent names. – Davis Herring Nov 11 '19 at 22:01