The code below is adapted from the answer here: https://stackoverflow.com/a/17579889/352552
My purpose in asking this question is try to to understand better how C++ handles type resolution around dependent types, versus what's considered to be on the current instantiation, and therefore not needing a typename
qualifier. I've been getting contradictory results from different compilers, so I've come here looking for a more canonical answer.
Consider this code
#include <iostream>
struct B {
typedef int result_type;
};
template<typename T>
struct C {
};
template<>
struct C<float> {
typedef float result_type;
};
template<typename T>
struct D : B, C<T> {
std::string show() {
//A) Default to current instantiation - ignore dependent type, even if one exists, or so I hope
D::result_type r1;
//B) What **exactly** does typename add, here?
//typename D::result_type r1;
return whichType(r1);
}
std::string whichType (int val){
return "INT";
}
std::string whichType (float val){
return "FLOAT";
}
};
int main() {
D<std::string> stringD;
D<float> floatD;
std::cout<<"String initialization "<<stringD.show()<<std::endl;
std::cout<<"Float initialization "<<floatD.show()<<std::endl;
}
line A) in show()
, if I understand correctly, tells the compiler to use the current instantiation, so I should get INT INT. On GCC, I do. So far, so good.
Line B, again if I understand correctly, should either tell the compiler to consider dependent types, which would make that line error out because of the ambiguity; or, if that means only consider dependent types, I should get INT FLOAT. On GCC I get INT INT there, too. Why?
Running this on Clang.
Line A doesn't compile at all.
error: no type named 'result_type' in 'D'; did you mean simply 'result_type'? D::result_type r1;
dropping the D::
does indeed yield INT INT.
Should it have compiled, or is Clang correct here?
Line B does indeed error on the ambiguity
error: member 'result_type' found in multiple base classes of different types typename D::result_type r1
Can anyone here say with authority which compiler (if any!) is canonically correct, and why?
Assuming Clang is correct, it might imply that
MyType::F
is invalid for referencing a type from the current instantiation if it exists on a base type; it's only valid if the type is defined on that class. Ie adding
typedef double dd;
to D
and then
D::dd d = 1.1;
std::cout<<d;
in show
would work just fine, which is indeed the case.
Moreover,
typename D::sometype
seems to mean consider dependent types, but not exclusively, and so expect errors if such a type winds up defined in multiple places, either in the current instantiation, and or dependent on a template parameter.
But again, this all assumes Clang's behavior is correct according to spec, which I can't speak to.
Link to GCC repl I was using: https://wandbox.org/
Link to Clang repl I was using: https://repl.it/languages/cpp11
Where did you get that idea?
typename
only means that what follows isn't a data member but a type name, so that the parsing of a template can be done. Do you know how primitive C++ compilers parsed template functions and classes in the old time? They did no meaningful parsing, they just ate all symbols doing only{
/}
balancing. Yes at some point you could include almost any garbage inside template definitions if they were never instantiated! It was simplistic and dirty but not that inane if you think about it, as the alternative (correct parsing) was not really practicable at the time.In order to even meaningfully parse (without even resolving many names) inside a template some things need to be made explicit: the category (variable-or-function, type name, template name) of symbols that can't be resolved before instantiation, so simple stuff like
X * Y;
,X * (Y);
andX(Y);
is ambiguous and not parsable (declaration or expression). Sotypename
is used to indicate that a symbol that can't be found at template definition time designate a type, so ifX
istypename T::U
then all three previous syntagmes are declaration; without thetypename
and ifT::U
is dependent they would be parse as expression-statements and there is no second parsing when templates are instantiated so ifU
was actually a type it would be an error.According to https://en.cppreference.com/w/cpp/language/dependent_name lookup from the "current instantiation" considers only non dependent base classes at definition time and then:
So hopefully what you "hoped" for shouldn't happen!