g++ doesn't like template method chaining on template var?
I'm trying to compile with g++ some code previously developed under Visual C++ 2008 Express Edition, and it looks like g++ won't let me call a template method on a reference returned by a method of a template variable. I was able to narrow the problem down to the following code:
class Inner
{
public:
template<typename T>
T get() const
{
return static_cast<T>(value_);
};
private:
int value_;
};
class Outer
{
public:
Inner const& get_inner() { return inner_; };
private:
Inner inner_;
};
template<typename T>
int do_outer(T& val)
{
return val.get_inner().get<int>();
}
int main()
{
Outer outer;
do_outer(outer);
return 0;
}
The code compiles fine under Microsoft's compiler, but g++ throws an error:
$ g++ -c main.cpp
main.cpp: In function ‘int do_outer(T&)’:
main.cpp:24: error: expected primary-expression before ‘int’
main.cpp:24: error: expected ‘;’ before ‘int’
main.cpp:24: error: expected unqualified-id before ‘>’ token
where line 24 refers to return val.get_inner().get<int>();
.
If I make do_outer
a normal method receiving an Outer
reference the code compiles. Making Inner::get()
a normal method also works. And making Inner::get()
return void and receive a template parameter also works because the int specifier below becomes needless, i.e.:
class Inner
{
public:
template<typename T>
void get(T& val) const
{
val = static_cast<T>(value_);
};
private:
int value_;
};
...
template<typename T>
int do_outer(T& val)
{
int i;
val.get_inner().get(i);
return i;
}
...
(g++ doesn't complaing about the code abo开发者_Python百科ve.)
Now I'm out of ideas. What's the problem? Is there a problem with gcc/g++? Is there a compliance issue with my code?
The compiler I'm using is:
$ g++ --version
g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3
Just to give some background on why the template
keyword is needed:
template<typename T>
int do_outer(T& val)
{
int i;
val.get_inner().get<int>(i);
return i;
}
When the compiler sees this function, it does not know what the type of val
is. It therefore parses the line val.get_inner().get(i)
as follows:
1: val .
The compiler sees the .
and so can assume that 'val' has class type and the next identifier is the name of a member object or function.
2. val . get_inner (
get_inner
is the name of the member and then the compiler sees the (
. The only possibility is that get_inner
is a function name and so this is a function call. It then parses the parameters until it finds the closing )
.
3. val . get_inner () .
As for the first step, it now knows that the return from get_inner must be a class type so it knows that the next identifier is a member object or function.
4. val . get_inner () . get <
So, what can the <
possibly mean? Of course it's the start of template arguments...or maybe it's the less than operator?
We know that get
can only be an object or a function. If it is an object then the <
makes perfect sense as the less than operator. Furthermore, the standard more or less states that only where the name before the <
is a template-name
will it treat the <
as template arguments (14.2/3):
After name lookup (3.4) finds that a name is a template-name, if this name is followed by a
<
, the<
is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.
In this case, the compiler has no idea what the type of the expression val.get_inner()
is and so it cannot lookup get
. It more or less assumes then that it's a member object and not a template-name. '<' is treated as the less than operator and the compiler ends up checking if get
is less than int
- hence the error.
So, why do the fixes work?
Adding the template
keyword
Literally we're telling the compiler that the get
is a template-name and so the <
operator is treated as the start of a template argument list.
Removing the template-arguments
When do_outer doesn't have the template arguments ie: val . get_inner () . get (
the compiler expects that the member get
is an object or a function. The (
disambiguates between these two and the name is treated as a function. Later template argument deduction then works out the type of the template parameter.
could you try with?
template<typename T>
int do_outer(T& val)
{
return val.get_inner().template get<int>();
}
I don't have access to gcc atm, but I've had similar issues and adding the template keyword always solved them. And it works in VS too.
I can't claim to be one of the, oh 10 people on the planet who fully understand C++ templates, but what you're doing here looks fine to me. (It fails with GCC 4.4.1 with the same error, BTW).
Changing do_outer
to
const Inner& inner = val.get_inner();
return inner.get<int>();
works with GCC and presumably will also work with Visual C++.
You might consider filing a bug with GCC; either they'll fix it, or it will be closed as INVALID and in the process someone will hopefully explain why what you're doing is not valid code.
A further update and AHA: It turns out it's not actually valid code, GCC just gives a horrible error message. Intel C++ outputs the (actually helpful!) error message:
template.cpp(24): error: type name is not allowed
return val.get_inner().get<int>();
Which made me realize the problem. Changing do_inner to
return val.get_inner().template get<int>();
the code is accepted by both ICC and GCC.
精彩评论