Hey, fuckface! Think fast!
template<typename F> void invokeFoo(F& f) {
f.foo();
}
struct Fooable {
void foo() {}
};
int main() {
Fooable c;
invokeFoo(c);
}
This is normal C++ code—nothing particularly nefarious afoot. invokeFoo is a function that takes
a hopefully-fooable argument and, well, calls its foo method. That’s fine and well, but what if we
further assume that F::foo is a member template? We might be tempted to write something like this:
template<typename F, typename P> void invokeFoo(F& f) {
f.foo<P>();
}
struct Fooable {
template<typename P> void foo() {}
};
int main() {
Fooable c;
invokeFoo<Fooable, int>(c);
}
You might be surprised to know that this actually fails to compile. Here’s an excerpt of what GCC says:
temp.cc: In function ‘void invokeFoo(F&)’:
temp.cc:2:12: error: expected primary-expression before ‘>’ token
2 | f.foo<P>();
| ^
temp.cc:2:14: error: expected primary-expression before ‘)’ token
2 | f.foo<P>();
| ^
Hey, that’s a new one. A parse error? How? We didn’t even do anything particularly offensive here—or so one might think. Let me add some suggestive whitespace to imply a different parse:
template<typename F, typename P> void invokeFoo(F& f) {
f.foo < P > (); // !
}
Does it make any god damn sense at all? No. But, you get the picture, I hope: that < could be a
less-than comparison operator! That sounds dumb, right? I mean, if I write
std::function<int()> f;, the compiler knows that I don’t mean to
perform an ordering comparison as suggested by
std::function < int() > f; (which would be valid for the right
definitions of std::function and f!) because it looks up std::function, sees that it’s a
template, and concludes that it should parse the stuff following it as a template parameter list.
So, what’s the difference here? Well, in invokeFoo, f has type F, which could be anything!
As such, there’s really no way to determine what F::foo might be. It doesn’t have to be a template!
It could just be a regular old data member, in which case the ordering comparison would be what we
want—and, sure, in most cases, this sort of thing is generally meant to parse as a template
parameter list, but you’d need an insane amount of look-ahead in the parser to be able to
determine even some cases. Even then, it might not be enough. Here—allow me to muddy the
waters with some well-formed C++:
template<typename F> void weird(F& f) {
F::foo < int() > f; // N.B. `int()` evaluates to 0
}
struct HasStaticFoo {
static int foo;
};
int HasStaticFoo::foo = 42;
bool operator>(bool b, const HasStaticFoo& f) { return true; }
int main() {
HasStaticFoo f;
weird(f);
}
Filthy, turbid, fetid water.
∗ ∗ ∗
Anyway, given all this, how do we signal to the compiler that we really mean for this to be interpreted as a template parameter list? Here’s what the great minds at C++ HQ came up with:
template<typename F, typename P> void invokeFoo(F& f) {
f.template foo<P>();
}
Gulp. Hey, are you saying you could’ve come up with something better? Anyway, the language we use
to refer to something like this is that f.foo is a dependent name. It can’t be looked up until we
have a better idea of the shape of the type F (on which f.foo depends), so the compiler needs a
little help in the meantime.
This problem has a similar analog for template types—one that you’re perhaps more likely to have seen before:
template<typename F> void weird() {
F::Nested();
}
What are the semantics here? Does F::Nested refer to a static member Nested of F, or does it
refer to a constructor of a nested class Nested declared inside F? It’s the difference between a type and a
value, and either could be valid. There’s no way to distinguish before instantiation, so the compiler will
interpret it as a function unless directed otherwise. If you want it to be interpreted as a nested
class instead, you’ll need to give the compiler a little encouragement:
template<typename F> void weird() {
typename F::Nested();
}
Moreover, if you want Nested to be read as a nested template class, the compiler demands even
more encouragement, the needy bastard:
template<typename F, typename P> void weird() {
typename F::template Nested<P>();
}
It’s not all bad—there’s been some progress in recent years on making these annotations optional in certain cases where there’s only one valid interpretation. For example, take a look at this case:
template<typename B> struct T : B::type {};
Technically, B::type is dependent on B, so traditional wisdom would suggest we ought to tack a
typename onto B::type. However, since B::type must be a type for this program to be valid,
the annotation is unnecessary (and, in fact, disallowed). That’s not new, though—what is new
is the generalization of this reasoning to a few other cases where nothing but a type-based
interpretation would make sense. One instance of this is for function return types:
// Valid no matter what:
template<typename T>
typename std::enable_if<true, T>::type foo() {}
// Valid only since C++20:
template<typename T>
std::enable_if<true, T>::type bar() {}
So, it really isn’t all bad. Unless you’re invoking a dependent function name. That shit still sucks. Oh well.
❦