Anyone dealing with templates will eventually run into something like this
// templated class with a type alias for "type" template <typename T> struct Obj { using type = int; }; template <typename T> void f() { Obj<T>::type var; // should be "int var;" }
But wait, there’s an error on the only statement in f()
# clang-3.6 error: expected ';' after expression Obj<T>::type var; ^ ; # gcc-5.1 error: need ‘typename’ before ‘Obj<T>::type’ because ‘Obj<T>’ is a dependent scope Obj<T>::type var; // should be "int var;"
gcc’s error is great in this case, telling us we need to add a leading typename to make it:
typename Obj<T>::type var;
The leading typename tells the compiler that Obj<T>::type
is a type. But why do we need this? We know that Obj<T>
is a dependent type because it depends on T.
First, you have to realize that without a leading typename, the compiler assumes (in the case of a dependent type) that it’s a static member variable, meaning the following is parsed correctly
template <typename T> struct Obj2 { static constexpr int type = 5; }; template <typename T> void f2() { int n = Obj2<T>::type * 3; }
So what? Why can’t the compiler just look inside of Obj
and see that it has a type
member. Well, in the case of template specializations, it can’t. Consider this:
// general case has "using type = int" template <typename T> struct Obj { using type = int; }; // specialization for int has "int type = 2" template <> struct Obj<int> { static constexpr int type = 2; };
Inside of f()
we don’t know which one of these we’re dealing with. f()
means it’s using the general template, but f()
uses the specialization. The programmer must clarify that they expect a type.
You might be thinking “right, but in this case it’s clear that the body of f()
is declaring a variable.” Right you are, but it’s not always this clear. What if instead we had
int x = 5; // global variable x template <typename T> void f() { Obj<T>::type * x; }
Now we have two possible versions of this
int * x; // declaring a local variable x of type int* 2 * x; // multiplication expression
Both of these lines make sense, but they mean very different things. It’s not always clear which way it should be parsed.
“Dot Template”
You’re less likely to see this problem, but I think it’s cool and has the same idea behind it. When your dependent name has a templated member function, the compiler gets confused about what you mean again:
struct Obj { template <typename U> void m(); };
Now let’s create a variable with dependent type Obj<T>
and call m()
template <typename T> void f() { Obj<T> v; v.m<int>(); }
But hold on errors! this time clang being more helpful
# gcc-5.1 error: expected primary-expression before ‘int’ v.m<int>(); # clang-3.6 error: use 'template' keyword to treat 'm' as a dependent template name v.m<int>(); ^ template
Huh? template keyword? What’s happening is the compiler doesn’t know that m
is a templated member function, we need tell it
v.template m<int>();
Alright so what’s the alternative here. Well, it’s a pretty well known issue with the C++ grammar that without context, you don’t know what certain lines mean. Borrowed from the D docs, consider the line:
A<B,C>D;
This could mean two things, (1) A is a templated class, B and C are template arguments, and D is a variable declared with that type. for example:
template <typename T, typename U> struct A; using A = int; using B = int; A<B,C> D; // A<int,int> D
Or (2), A, B, C, and D are all variables and this is two comparisons separated by a comma
int A{}, B{}, C{}, D{}; (A < B), (C > D);
So, by default the compiler is assuming that m
is not a template, which means that it assumes the angle brackets are less than and greater than operators, so it’s parsed as
((v.m) < int) > ();
This could make sense in a different context
template <typename T> struct Obj { int m; };
And then the following:
int a{}; v.m<a>0; ((v.m < a) > 0
So the “dot template” is needed to say that what follows is a template. Like I said this is far less common, and the syntax is so awkward that it can drive design decisions. If you’ve ever wondered why std::get
is a free function, a big part is that std::tuple
is often used as a dependent type.
template <typename... Ts> void f(std::tuple<Ts...>& t) { std::get<0>(t); // nice t.template get<0>(); // gross }