How to Understand Dependent Names in C++

How do you understand dependent names in C++

A dependent name is essentially a name that depends on a template argument.

When using templates there is a distinction between the point of definition of the template and the point of instantiation i.e. where you actually use the template. Names that depend on a template don't get bound until the point of instantiation whereas names that don't get bound at the point of definition.

A simple example would be:

template< class T > int addInt( T x )
{
return i + x.toInt();
}

where a declaration or definition of i would need to appear before the definition given above since i does not depend on the template argument T and is therefore bound at the point of definition. The definition of the toInt member of the as-yet-unknown-type x variable only has to appear before the addInt function is actually used somewhere as it is a dependent name ( technically the point of instantiation is taken as the nearest enclosing global or namespace scope just before the point of use and so it has to be available before that ).

What is the definition of dependent name in C++?

Thanks to Declarations and where to find them being accepted into C++23, there is now an explicit enumeration of categories of dependent names, which seems to cover the code in the question.

In N4919 [temp.dep.general]/2 it is stated that

[...] The component name of an unqualified-id (7.5.4.2) is dependent if

  • it is a conversion-function-id whose conversion-type-id is dependent, or
  • it is operator= and the current class is a templated entity, or
  • the unqualified-id is the postfix-expression in a dependent call.

And in [temp.dep.type]/5 the rules for when qualified names are dependent are given:

A qualified name (6.5.5) is dependent if

  • it is a conversion-function-id whose conversion-type-id is dependent, or
  • its lookup context is dependent and is not the current instantiation, or
  • its lookup context is the current instantiation and it is operator=, or
  • its lookup context is the current instantiation and has at least one dependent base class, and qualified name lookup for the name finds nothing (6.5.5).

Regarding the example of t.bar() given in the question, the name bar is described in the referenced section (6.5.5) ([basic.lookup.qual]) as a "member-qualified-name". Furthermore, [basic.lookup.qual]/2 explains that its "lookup context" is "the type of its associated object expression (considered dependent if the object expression is type-dependent)". Clearly t is type-dependent, and it is the lookup context for bar, so [temp.dep.type]/5.2 applies, and bar is indeed a dependent qualified name.

What are dependent names in C++?

If I'm reading https://en.cppreference.com/w/cpp/language/dependent_name correctly, both B and B::C are dependent.

But B also "refers to the current instantiation", which allows it to be used without typename. It seems to be a fancy way of saying that "whether it's a type can't be affected by specializations".

You can specialize B to make B::C not a type:

template <>
struct some_struct<int>::B
{
static void C() {}
};

(interestingly, a partial specialization didn't compile)

But you can't prevent B from being a type via specializations.

Lookup of dependent names in C++ template instantiation

tl;dr Foo<int> doesn't invoke any ADL, but Foo<X> would (where X is a class type).


First of all, in this code foobar is a dependent name because of (C++14/N3936) [temp.dep]/1

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 type-dependent expression (14.6.2.2), or

and t is a dependent name because it is part of a declaration T t where T is a template parameter and thus a dependent type.

Moving onto dependent name resolution, there is [temp.dep.res]/1 which introduces the fact that names can be both looked up in the definition context, and the instantiation context, and defines where the instantiation context is. I have omitted that for brevity, but in this example template class Foo<int>; is the point of instantiation.

The next bit is [temp.dep.candidate]/1:

For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that:

  • For the part of the lookup using unqualified name lookup (3.4.1), 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.

Those last two parts are the "two phases" of two-phase lookup. (Note - this section changed in wording from C++11 to C++14, but the effect in the same).

In the first phase, 3.4.1, no names are found for foobar.


So we move onto the second phase. The actual places that names are looked up as described in 3.4.2. The text is long but the here are two of the relevant rules:

  • If T is a fundamental type, its associated sets of namespaces and classes are both empty.

  • If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. [...]

So when you instantiate Foo<int>, then the second phase of lookup does not introduce any additional namespaces to search.

However, if you change your example to have struct X {}; and then change int to X everywhere, then the code does compile. This is because of the latter bullet point: ADL for an argument of class type does search the enclosing namespace of that class (which is the global namespace now), however ADL for an argument of built-in type does not search the global namespace.

Dependent name and scope

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.



Postponed lookup of dependent names allows for ADL to find names declared after the function template definition

All non-dependent names in a function template definition must be declared at the point of the definition. Dependent names need only be declared at the time of the first instantiation, for a given specialization, if they can be found via argument-dependent lookup (ADL) for the particular instantiation. In your first example, g is a dependent name as it depends on the type template parameter, and it can moreover be found via ADL on ::Q, and thus the first example is well-formed. In your second example g is not a dependent name as it does not depend on the type template parameter, and as the name is moreover not visible at the point of the function template definition, the second example is ill-formed.

We can summarize the rules quoted from the relevant standard passages (see Section Details) as:

  • Lookup for non-dependent names follows the usual lookup rules, and thus the name need to be declared at the point of the function template definition,
  • Lookup for dependent names is postponed until a given instantiation. However, at the time of this instantiation, only argument-dependent lookup (ADL) will be able to find names that were not declared at the point of the function template definition, but were declared at the point of the given instantiation.

The rules of latter bullet means that the following example is well-formed:

namespace ns {

template<typename T> T f(T a) {
// Can find ::ns::g(Q) only via ADL on T for
// an instantiation of f with T == ::ns::Q.
return g(a);
}

class Q {};

Q g(Q e) { return e; }

} // namespace ns

int main() {
(void) f(ns::Q{});
return 0;
}

as is the following example:

template<typename T> T f(T a) {
// Can find ::ns::g(Q) only via ADL on T for
// an instantiation of f with T == ::ns::Q.
return g(a);
}

namespace ns {

class Q {};

Q g(Q e) { return e; }

} // namespace ns

int main() {
(void) f(::ns::Q{});
return 0;
}

whereas the following example is ill-formed, as ADL cannot find g for an instantiation of ::ns::Q:

template<typename T> T f(T a) {
// Cannot find ::g(Q) as ADL on T for
// an instantiation of f with T == ::ns::Q
// will only consider the ::ns namespace.
return g(a);
}

namespace ns {

class Q {};

} // namespace ns

::ns::Q g(::ns::Q e) { return e; }

int main() {
(void) f(::ns::Q{});
return 0;
}

For the latter, the compiler (in this case, Clang) even presents an instructive error message as for why the program is ill-formed:

error: call to function 'g' that is neither visible in the template
definition nor found by argument-dependent lookup


Details

[temp.res]/9 states [extract, emphasis mine]:

[temp.res]/9 When looking for the declaration of a name used in a template
definition, the usual lookup rules ([basic.lookup.unqual],
[basic.lookup.argdep]) are used for non-dependent names. The lookup
of names dependent on the template parameters is postponed until the
actual template argument is known
([temp.dep]). [ Example: ... ]
[...]

[temp.dep.res]/1 is clear that only declarations that are visible at the point of definition of the template are considered for non-qualified (dependent) name lookup [emphasis mine]:

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

  • (1.1) Declarations that are visible at the point of definition of the template.
  • (1.2) Declarations from namespaces associated with the types of the function arguments both from the instantiation context ([temp.point])
    and from the definition context.

a fact that is repeated in [temp.dep.candidate]/1 [emphasis mine]:

[temp.dep.candidate]/1 For a function call where the postfix-expression is a dependent name,
the candidate functions are found using the usual lookup rules
([basic.lookup.unqual], [basic.lookup.argdep]) except that:

  • (1.1) For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.
  • (1.2) For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either
    the template definition context or the template instantiation context
    are found.

Why can a dependent name be considered as complete even if the actual type is not defined until the very end

I believe this program is ill-formed, no diagnostic required.

[temp.point]/8 reads, editing out the irrelevant parts:

A specialization for a function template [...] may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

YeapImpl<X> has two points of instantiation: where it is called on the commented line in the question and at the end of the translation unit. In the first point of instantiation, X is incomplete which would make the body of the function ill-formed. In the second point of instantiation, X is complete which makes the body well-formed.

Those two specializations have [very] different meanings.

Dependent template names and C++20 ADL

This is P0846, which is a C++20 feature. It appears that gcc does not implement this yet.

It's not a question of dependent name or not, it's a question of does the compiler know that foo refers to a template or not, and so is foo< doing a comparison or is it starting to do template parameters.

In C++17, the compiler had to already know that foo was a template-name (which you could accomplish by adding using N::foo;) in order to perform ADL, in C++20 that's no longer true - now the rule is that if unqualified lookup finds a template or nothing, we also consider it to be a template.


The dependence of foo didn't change as a result of this paper. In foo<0>(c);, foo is still a dependent name. The rule in [temp.dep] from C++17 was:

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

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

The second bullet applies here - c is type-dependent. The C++20 wording is the same. The issue here wasn't that foo wasn't dependent in C++17. It's just that the rule was when we foo<, we don't know that foo is a template, so it's considered the less-than-operator, and that then fails.

The template disambiguator for dependent names

Short version

Don't rely on this. Use the template keyword as you're supposed to, don't try such an obscure hack only to avoid a few keystrokes. Your code should definitely not compile according to the standard, and the example you quoted from cppreference.com may soon become explicitly disallowed as well (I don't think it was valid in the first place). GCC and Clang yield different results for both these examples. Even if they compile today, they may fail tomorrow in the next compiler version.

Long version

Regarding the example from cppreference.com, let's first note that Clang 3.6.0 compiles the code, but GCC 5.1.0 rejects both s.set<T>() and s.template set<T>() with the error invalid use of 'class std::set<T>'. I don't think either of the two compilers does the right thing here according to the standard, but, intuitively, GCC's error message makes a lot of sense: what would be the meaning of s.set<T>() with set<T> being a class template specialization?

With your code, it's the other way around: Clang rejects it (the error message quoted in the question seems to be actually from Clang) and GCC compiles it.

The examples rely on paragraph [3.4.5p1] in the standard (emphasis mine in all quotes):

In a class member access expression (5.2.5), if the . or -> token is
immediately followed by an identifier followed by a <, the identifier
must be looked up to determine whether the < is the beginning of a
template argument list (14.2) or a less-than operator. The identifier
is first looked up in the class of the object expression. If the
identifier is not found, it is then looked up in the context of the
entire postfix-expression and shall name a class template.

The class template part is the reason for your code being rejected by Clang (your func is a function template). Function templates were removed from there as the resolution of defect 141, included in C++11. It's worth mentioning a comment in the defect report:

There do not seem to be any circumstances in which use of a non-member
template function would be well-formed as the id-expression of a class
member access expression.

I think this says something about the intent of this lookup rule: it's supposed to find constructs that are well formed; it's not intended to just make the parser happy about those <> with some temporary match that will later be replaced by something else with entirely different semantics.

Even with the special lookup rule above, I'm not sure the standard allows you to omit the template in such cases. Paragraph [14.2p4] says:

When the name of a member template specialization appears after . or
-> in a postfix-expression or after a nested-name-specifier in a qualified-id, and the object expression of the postfix-expression is
type-dependent or the nested-name-specifier in the qualified-id refers
to a dependent type, but the name is not a member of the current
instantiation (14.6.2.1), the member template name must be prefixed by
the keyword template. Otherwise the name is assumed to name a
non-template.

Both member templates set and func in the two examples satisfy the conditions in there, respectively. As you can see, there's no mention of an exception to this rule.

Or, to put it another way, if you want set to be resolved as the name for the member template, it has to have template in front of it. If it doesn't, it can be resolved as the namespace-scope set, but then the set name itself is no longer a name dependent on template parameters (set<T> is still dependent, but set itself is not). And then we get to [14.6p10], which says:

If a name does not depend on a template-parameter (as defined in
14.6.2), a declaration (or set of declarations) for that name shall be in scope at the point where the name appears in the template
definition; the name is bound to the declaration (or declarations)
found at that point and this binding is not affected by declarations
that are visible at the point of instantiation.

Once bound, it's carved in stone and the later switch to the member template is not valid, which makes the cppreference.com example incorrect.

Moreover, there's an open issue regarding the applicability of the lookup rule from [3.4.5p1] to such examples - issue 1835. Quoting a note in there:

One possibility might be to limit the lookup to the class of the
object expression when the object expression is dependent.

The issue is in drafting status, which means informal consensus has been reached in the working group. Exactly what that consensus was remains to be seen, but I'd say there's a good chance that something will change. Relying on such code doesn't seem like a good idea.


The quoted paragraphs have remained unchanged since C++11 up to the current working draft (N4431).



Related Topics



Leave a reply



Submit