Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A issue about the second phase name lookup for default argument

#include <iostream>
namespace J {
    template <typename T> void zip(int = zap([] { })) { }  //#1
    template <typename T> int zap(const T &t) {return 0; }
}
int main(){
    J::zip<long>(); 
}

Consider the above code, that's a simplified example of proposed resolution 1664. Note the place marked with #1, I doubt why the name for zap can be looked up in the context of instantiation. I think zap is not a dependent name, the definition of dependent name are the following:
temp.dep

In an expression of the form:

postfix-expression ( expression-list opt)
where the postfix-expression is an unqualified-id, the unqualified-id denotes a dependent name if

  • any of the expressions in the expression-list is a pack expansion,
  • any of the expressions or braced-init-lists in the expression-list is type-dependent, or
  • the unqualified-id is a template-id in which any of the template arguments depends on a template parameter.

I think zap([] { }) does not satisfied any of the above conditions due to the type of expression [] { } is not a dependent-type. Although the following rule says that the namespace associated with closure-type is determined as the following:
temp.inst#11

If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared ([expr.prim.lambda.closure]) – and therefore its associated namespaces – remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.

However, these names both from the context of template definition and the context of instantiation are only considered for the dependent-name, which is ruling by:
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.
  • Declarations from namespaces associated with the types of the function arguments both from the instantiation context ([temp.point]) and from the definition context.

temp.nondep

Non-dependent names used in a template definition are found using the usual name lookup and bound at the point they are used.

So, I think, in order to obey these aforementioned rules, the name lookup for zap only occurs at the point it is used(namely, at #1) due to it's not a dependent-name, that is, the names from the context of instantiation(ADL) are not considered at all.

I test the code in three implementations, the outcomes are listed in the following:

  1. Clang9.0 and higher version views zap as a dependent-name.
  2. The version under 8.0 of Clang reports an error that makes no sense.
  3. The version under 9.1 of gcc views zap as a dependent-name.
  4. The version higher than 9.1 of gcc views zap as a non-dependent name and do not perform the name lookup for zap in the context of instantiation.

So, what is exactly process does the name lookup for zap undergo? It seems the latest version of GCC agrees with considering zap as a non-dependent name which results in that it can't find names for zap. If I miss other rules in the standard, I would appreciate you to point it out.

like image 568
xmh0511 Avatar asked Aug 21 '20 02:08

xmh0511


1 Answers

Your diagnosis is correct, I think, especially given that the example in [temp.nondep] is very similar.

At #1, zap is an unqualified name that is used in a function call expression, within a template definition. It is not a dependent name, so it must be bound at the point of definition. There is no ::J::zap or ::zap in scope (yet), nor could it be found by ADL at this point. This would be different if, for example, the argument type was declared in a namespace that would be overlooked by unqualified name lookup:

namespace I { 
struct nondep {};

template <typename T>
int zap(const T&) { return 0; } 
}

namespace J {
template <typename T>
void zip(int = zap(I::nondep{})) { }  // #1
// 1. Unqualified name lookup of zap finds nothing,
// 2. ADL considers namespace I and finds only I::zap,
// 3. Overload resolution succeeds:
// zap is bound to I::zap<I::nondep>
    
template <typename T>
int zap(const T&) { return 0; } // Not considered
}

template <typename T>
int zap(const T&) { return 0; } // Not considered

int main() {
    J::zip<long>(); 
}

To illustrate the difference with dependent names, here is a slightly modified example:

namespace J {
struct nondep {};

template <typename T>
int zap(T) { return 2; }

template <typename T>
int zip(int i = zap(T{})) { return i; }  // #1, zap is dependent
}

struct nondep {};

template<typename T> int zap(T) { return 1; }

int main() {
    // Unqualified name lookup for zap finds J::zap, 
    // ADL additionally finds ::zap,
    // Overload resolution fails:
    // this call to zap would be ambiguous
    // int x = J::zip<nondep>(); 

    // But here, unqualified lookup and ADL only find J::zap, which is selected
    int y = J::zip<J::nondep>(); // y == 2
}

I'm not entirely sure about the scope of the closure type of the lambda, though. According to the wording in [expr.prim.lambda.closure],

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression.

It doesn't mention function parameter scope, which would have to place it in namespace J, though regardless of this, I think the compiler should be able to determine that []{} is not dependent on typename T.

like image 190
sigma Avatar answered Oct 29 '22 18:10

sigma