One Example That Explains Dependent Names
In C++, a name is dependent if its meaning or type, value, or definition lookup depends on, a template param T
. Some examples include:
std::vector<T>
: namestd::vector
is not dependent.std::vector<T>
is dependentstd::vector<T>::iterator
(type of::iterator
depends on T)t.do_something()
(member lookup depends on T)sizeof(T)
(value depends on T)
Also, In C++, template code is compiled in two stages:
- parsing and template definition: the compiler checks syntax and all non-dependent names directly
- instantiation: after a set of template args have been provided,
- All dependent names are looked up
- Overloaded resolution is performed, template code are generated
Some of the dependent names require keywords template
or typename
so the parser knows how to read the token sequence in the parsing phase. This id due to grammatical ambiguities, like:
t.do_something<int> ❌
,<
could also mean “smaller than”.
In those cases, we need to specify template
to disambiguate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
template <typename T>
struct Foo{
void do_something(){}
struct Iterator{};
};
template <typename T>
void call_foo_func(T t){
t.do_something(); // Works for a regular member function, like Foo<T>::do_something after deduction. Its lookup is in
}
struct Bar{
template <typename B>
void do_something(){} // this is a member template: the class itself is not a template, but the method is.
};
template <typename T>
void call_bar_func(T t){
// Works for member templates, to disambiguate (for the sake of '<')
// Member templates are depedent on T
t.template do_something<int>(); // Not working on Foo<T>::do_something() because it is not a member template
}
int main()
{
printf("Hello World");
Foo<int> f;
call_foo_func(f);
Bar b;
// call_foo_func(b); // ❌
call_bar_func(b);
// call_bar_func(f); ❌
return 0;
}
So in the example above,
- Inside
call_foo_func
the expression t.do_something() is dependent; oncecall_foo_func
is instantiated withFoo<int>
the usual overload resolution chooses the regular member function. There’s no ambiguity in parsing. template <typename T> struct Foo
is a class template,Bar::do_something<T>
is a member template of Bar.- There’s an ambiguity in interpreting
<
- Therefore, we need to disambiguate by specifying
template
in ‘t.template do_something();'. - Otherwise, in parsing,
do_something<int>()
could be interpreted as “member attribute do_something is smaller than int” , which raises a parsing error.
- Otherwise, in parsing,
- There’s an ambiguity in interpreting
Disambiguate Type With Typename
When there are grammatical ambiguity of dependent types on T, we need to specify typename
to the compiler :
1
2
3
4
template <typename T>
void call_func(T t){
typename std::vector<T>::iterator it;
}
Do you see why std::vector<T>::iterator
needs to be disambiguated? Because iterator
could be also interpreted a static variable:
1
2
3
4
5
template <typename T>
struct Weird {
static int iterator; // could exist!
using iterator_type = int;
};
Also, typename
should come right before the expression, like:
1
std::unordered_map<Key, typename std::list<std::pair<Key, Value>>::iterator> itr_lookup_;
One example that doesn’t need typename
: if the parser sees <
with no problem, it knows >
must come:
1
2
3
4
template <typename Key, typename Value>
class LRUHashMap{
std::list<std::pair<Key, Value>> cache_;
}