views:

2120

answers:

4

It had been a while since GCC caught me with this one, but it just happened today. But I've never understood why GCC requires typedef typename within templates, while VS and I guess ICC don't. Is the typedef typename thing a "bug" or an overstrict standard, or something that is left up to the compiler writers?

For those who don't know what I mean here is a sample:

template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
    std::map<KEY,VALUE>::const_iterator iter = container.find(key);
    return iter!=container.end();
}

The above code compiles in VS (and probably in ICC), but fails in GCC because it wants it like this:

template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
    typedef typename std::map<KEY,VALUE>::const_iterator iterator; //typedef typename
    iterator = container.find(key);
    return iter!=container.end();
}

Note: This is not an actual function I'm using but just something silly that demonstrates the problem

+15  A: 

Well, GCC doesn't actually require the typedef -- typename is sufficient. This works:

#include <iostream>
#include <map>

template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
    typename std::map<KEY,VALUE>::const_iterator iter = container.find(key);
    return iter!=container.end();
}

int main() {
    std::map<int, int> m;
    m[5] = 10;
    std::cout << find(m, 5) << std::endl;
    std::cout << find(m, 6) << std::endl;
    return 0;
}

This is an example of a context sensitive parsing problem. What the line in question means is not apparent from the syntax in this function only -- you need to know whether std::map<KEY,VALUE>::const_iterator is a type or not.

Now, I can't seem to think of an example of what ...::const_iterator might be except a type, that would also not be an error. So I guess the compiler can find out that it has to be a type, but it might be difficult for the poor compiler (writers).

The standard requires the use of typename here, according to litb by section 14.6/3 of the standard.

Magnus Hoff
Here, too, I think you mean KEY,VALUE in the line that starts with "typename". With that change, it compiles for me. :)
unwind
I saw your comment on the question and fixed it just now :)
Magnus Hoff
yes, it is indeed required. for a reference see 14.6/3
Johannes Schaub - litb
for a good faq, consider http://womble.decadentplace.org.uk/c++/template-faq.html#disambiguation
Johannes Schaub - litb
..::iterator can reference an static member.
Ismael
@xhantt you're actually right, now that you mention it
Robert Gould
dribeas, have you actually read his answer? he literally says "standard requires the use of typename".
Johannes Schaub - litb
i tested the code, and yeah he needs const_iterator . i've overlooked that too. but it's really too easy to overlook imho :)
Johannes Schaub - litb
oh wait. the .map() was also wrong in the original question. and ::iterator too. i'm not sure whether it's right to correct it in the answer then as i did. anyway i will leave it as is and let magnus/robert decide on that. cheers!
Johannes Schaub - litb
litb: Thanks for the corrections and edits. :)
Magnus Hoff
xhantt: Yes, it could be a static member, but that would (as I say in my answer) be an error. Given no other context, in the line "std::map<KEY,VALUE>::const_iterator iter = container.find(key);", "...::const_iterator" *must* be a type, if we want it to compile. Is that not right?
Magnus Hoff
indeed. the typename is used to help the compiler do analysis. but the compiler is actually not required to check any syntax within a template if it's not instantiated. whether and when the compiler signals an error (even if you write "; +;") or not is a question of its quality of implementation.
Johannes Schaub - litb
the only requirement is that the compiler signals an error (if there is something wrong) (actually, only some kind of diagnostic, could be a warning too. the standard does not know a distinction between "warning" and "error") at instantiation time (like, if you actually call the function template).
Johannes Schaub - litb
btw, i'm sorry if "dribeas, have you actually read his answer?" sounded a little offensive. i was just quite confused as to where he is saying it's not required. :) no offence, of course.
Johannes Schaub - litb
Indeed both are good answers, but Dribeas had really good(concise) example of a real conflict of meanings for iterator.
Robert Gould
Robert: You are of course free to decide which answer helped you the most :)
Magnus Hoff
@litb: I had read the answer twice, and nonetheless I had not actually _read_ it. I somehow read that typename was not required, instead of what the Magnus had actually said: typedef is not actually required.'Attention deficit reader' is a nice way of putting it :)
David Rodríguez - dribeas
I have already removed the downvote, and my dearest excuses Magnus.
David Rodríguez - dribeas
+3  A: 

It looks like VS/ICC supplies the typename keyword wherever it thinks it is required. Note this is a Bad Thing (TM) -- to let the compiler decide what you want. This further complicates the issue by instilling the bad habit of skipping the typename when required and is a portability nightmare. This is definitely not the standard behavior. Try in strict standard mode or Comeau.

dirkgently
It would be a Bad Thing if the compiler did so willy-nilly. In fact, it's doing this only on broken code. There's actually no prohibition in the standard against compiling broken code. Should still be a warning (diagnostic) though.
MSalters
A: 

This is a bug in the Microsoft C++ compiler - in your example, std::map::iterator might not be a type (you could have specialised std::map on KEY,VALUE so that std::map::iterator was a variable for example).

GCC forces you to write correct code (even though what you meant was obvious), whereas the Microsoft compiler correctly guesses what you meant (even though the code you wrote was incorrect).

Joe Gauterin
Actually, it looks like MSVC will check to see whether std::map::iterator is a type or not before deciding. I don't have a copy of the standard but this seems like non-conforming behaviour, but it only means that it will (try to) correct and compile some incorrect programs, not introduce errors into correct ones.
Sumudu Fernando
Yes, it's a bug because the compiler doesn't issue a diagnostic for illegal code.
Joe Gauterin
+14  A: 

The typename is required by the standard. Template compilation requires a two step verification. During the first pass the compiler must verify the template syntax without actually supplying the type substitutions. In this step, std::map::iterator is assumed to be a value. If it does denote a type, the typename keyword is required.

Why is this necessary? Before substituing the actual KEY and VALUE types, the compiler cannot guarantee that the template is not specialized and that the specialization is not redefining the iterator keyword as something else.

You can check it with this code:

class X {};
template <typename T>
struct Test
{
   typedef T value;
};
template <>
struct Test<X>
{
   static int value;
};
int Test<X>::value = 0;
template <typename T>
void f( T const & )
{
   Test<T>::value; // during first pass, Test<T>::value is interpreted as a value
}
int main()
{
  f( 5 );  // compilation error
  X x; f( x ); // compiles fine f: Test<T>::value is an integer
}

The last call fails with an error indicating that during the first template compilation step of f() Test::value was interpreted as a value but instantiation of the Test<> template with the type X yields a type.

David Rodríguez - dribeas
Good example of failure case
Robert Gould
I think you mixed up your comments on the two calls to `f`, `f( X() );` succeeds while `f( 5 );` is a compile error.Anyway, MSVC handles this alright -- it seems to delay the decision of whether `Test<T>::value` is a value or a type until the template has been instantiated.It doesn't do this for members of a class template, however.
Sumudu Fernando
@Sumudu: you are right, I also corrected the `f( X() )` call into the more explicit code above. If MSVC delays the check until the type is instantiated then MSVC is not complying with the standard.
David Rodríguez - dribeas