I am playing a bit with static polymorphism, I'm calling a function which internally calls the "right" specialized function depending on the type of the initial argument (basically I'm doing tagging). Here is the code:
#include <iostream>
using namespace std;
// tags
struct tag1{};
struct tag2{};
// the compliant types, all should typedef tag_type
struct my_type1
{
using tag_type = tag1;
};
struct my_type2
{
using tag_type = tag2;
};
// static dispatch via tagging
template <typename T>
void f(T)
{
cout << "In void f<typename T>(T)" << endl;
// why can I call f_helper without forward definition?!?
f_helper(typename T::tag_type{});
}
int main()
{
my_type1 type1;
my_type2 type2;
// how does f below knows about f_helper ?!?!
// even after instantiation f_helper shouldn't be visible!
f(type1);
f(type2);
}
// helper functions
void f_helper(tag1)
{
cout << "f called with my_type1" << endl;
}
void f_helper(tag2)
{
cout << "f called with my_type2" << endl;
}
So, f(T)
is called with a parameter my_type1
or my_type2
that internally must typedef tag_type
with the appropriate tag tag1
/tag2
. Depending on this internal tag_type
, the "right" wrapper is then called, and this decision is made of course at compile time. Now I really don't understand why this code IS working? Why don't we need to forward-declare f_helper
? I first had the wrappers defined before main
(and after f
), and I though ok, this makes sense, you don't need to forward declare because the compiler instantiate the template only when f(type1);
is called (in main()
), before it doesn't know the type T
, so at the time of instantiation the compiler knows f_wrapper
.
But as you see, even if I declare the wrappers AFTER main()
, the code still works. Why is this happening? I guess the question is a bit strange, asking why a code works :)
EDIT
The code continues to compile even in gcc5 and gcc HEAD 6.0.0.
I agree, the code is ill-formed. I'm surprised neither g++ nor clang++ has even a warning about this.
14.6.2/1:
[
f_helper
is a postfix-expression and id-expression, andtypename T::tag_type{}
is type-dependent, sof_helper
is a dependent name.]14.6.4/1:
14.6.4.1/6:
14.6.4.2/1:
f_helper(typename T::tag_type{})
is a type-dependent expression becauseT::tag_type
is a dependent type. This means thatf_helper
doesn't need to be visible untilf<T>
is instantiated due to two phase lookup.EDIT: I'm pretty sure that this is actually undefined behaviour. If we look at 14.6.4.2 [temp.dep.candidate] we see this passage:
The last paragraph to me indicates this is undefined behaviour. The
function call that depends on a template parameter
here isf_helper(typename T::tag_type{})
.f_helper
isn't visible whenf
is instantiated, but it would be if we performed name lookup after all translation units have been compiled.The call to
f_helper(typename T::tag_type{});
is dependent on the template parameterT
, so the namef_helper
need not be visible until the point of instantiation off<T>
(due to two phase name lookup).I believe the code works because implementations are allowed to delay the point of instantiation of function templates until the end of the translation unit, at which time the definitions for
f_helper
are available.N3936 §14.6.4.1/8 [temp.point]