One change surprised me, though. The following pattern is no longer accepted by gcc:
gcc now generates the following error:template <typename T>
class Producer
{
public:
/// ...
};
template <typename T>
class Consumer
{
public:
typedef Producer<T> Producer;
};
void ThisWontCompile()
{
Producer<int> producer;
Consumer<int> consumer;
}
test.cpp:13: error: declaration of 'typedef class ProducerWe use this pattern quite a lot in our code, and it seems reasonably tasteful. Certainly, it seems morally equivalent to the following pattern:Consumer ::Producer'
test.cpp:4: error: changes meaning of 'Producer' from 'class Producer'
...in the sense that they both change the meaning of a name within the scope of the second class.class Foo
{
public:
/// ...
};
class Bar
{
public:
void Foo() {}
};
void ThisCompiles()
{
Bar bar;
}
Now I'm no C++ language lawyer, but I've yet to find a compelling part of the standard that shows this to be bad C++ code.
Update
Commenter jd pointed out the solution to this conundrum. The Producer example above will compile if the typedef is written slightly differently, thus:
This shows that the gcc change was not as drastic as I first feared, and the fix is still very valid (and very readable) C++ code.template <typename T>
class Producer
{
public:
/// ...
};
template <typename T>
class Consumer
{
public:
typedef ::Producer<T> Producer;
};
void ThisDoesCompile_Hurrah()
{
Producer<int> producer;
Consumer<int> consumer;
}
The problem was gcc looking up the name Producer in the scope of the Consumer class, and finding the Producer typedef name that was in the process of being defined. A subtle problem with a non entirely useful error message (but we're used to those, aren't we?).
Although the other compilers I have tried (MSVC and Comeau) accept the original code, I'm not sure if it is correct according to the letter of the C++ standard or not.
10 comments:
Those two are not equivalent...
The problem is that you have a template, Producer<T>, and then you want to have a type Producer. The "morally equivalent" code doesn't do that, does it?
Did I miss something?
Why would a typedef and a function be morally equivalent? And you wrote a programming book?
Just change the typedef name.
Clearly a typedef and a function are not technically equivalent in any way. However, I say they are morally equivalent because inside the Consumer/Bar class, you have changed the meaning of a name in both cases. gcc allows one kind of name change, but not the other.
If you want something closer technically, as well as morally, you could use this example:
class Foo {};
class Bar
{
public:
class Foo {};
void WhichFooIsUsedInHere()
{
Foo foo;
// Bar::Foo of course
}
}
That compiles fine under gcc 4.3.2. It's not ambiguous.
The more I look at this, the more I think that the Producer/Consumer example is valid C++ and that the latest gcc is doing something weird. But, as I say, I'm not a C++ lanugage lawyer, so I won't bet my life on it.
Many other compilers accept the questionable code, BTW.
And, of course, I have changed the template names throughout our codebase. But when maintaining a large codebase, having to make such modificatons to a prevelent pattern is not ideal, if the code actually is valid C++.
It's worth pointing out why simply changing the typedef name is not my preferred solution to get this code to compile. It's down to the motivation for using the typedef in the first place...
It's hard to show the importance of the typedef in such a small, sanitised code snippet. But I'll try to explain.
Whenever any of the implementation of Consumer<T> refers to an instance of the Producer class (and it does so a lot), it's always talking about a Producer<T>. So the typedef is setting up a more convenient name for Producer<T>.
Imagine this with various different (more descriptive) names for "Producer" and for "T" and you'd begin to see why the typedef is a useful construct in this case.
This is a pattern used across many, many pairs (or triplets, or...) of classes.
The typedefs help make much more readable code.
So in gcc 4.3.x, having to name the typedef something other than Producer isn't making the code easier to write and to read any more. It's made it harder, since there's another name to understand now. It would probably be easier to stick to using Producer<T> throughout the code.
Sigh.
ISO 14882-2003, 3.3.6, basic.scope.class
2) A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S.
Perhaps I'm being stupid, but I don't see how 3.3.6, basic.scope.class applies to this problem. I'm not sure that is does.
Could you explain how 3.3.6explains the behaviour I described, and why template constructs are not allowed by non-template constructs are?
One work around appears to be to fully qualify the namespace of the type used in the typedef. So to use your earlier example and add in an explicit global namespace reference:
template <typename T>
class Producer
{
public:
/// ...
};
template <typename T>
class Consumer
{
public:
typedef ::Producer<T> Producer;
};
void ThisWillCompile()
{
Producer<int> producer;
Consumer<int> consumer;
}
jd - you are spot on. Many thanks.
So the gcc change is a lot less dramatic than it at first appeared. Indeed, it does seem sensible to qualify which "Producer" class you are typedefing inside Consumer.
Hai Pete,
Nice information you have got here. I am not a big user of templates.
But how did you come across with this problem?
Actually i got the same problem while compiling stl with various versions of gcc. So thanks for the info. I found these problem in stl_tree.h file. How did they managed to avoid this problem in new stl_tree.h with new versions of stl. You have any idea?
Hai pete,
I think -fpermissive option for gcc is also helpful to change the error to warnings.Please check.
Best Regards,
Susmith M R
Post a Comment