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 above.)
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
could you try with?
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.
Just to give some background on why the
template
keyword is needed:When the compiler sees this function, it does not know what the type of
val
is. It therefore parses the lineval.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 thatget_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 atemplate-name
will it treat the<
as template arguments (14.2/3):In this case, the compiler has no idea what the type of the expression
val.get_inner()
is and so it cannot lookupget
. 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 ifget
is less thanint
- hence the error.So, why do the fixes work?
Adding the
template
keywordLiterally 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 memberget
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.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
toworks 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:
Which made me realize the problem. Changing do_inner to
the code is accepted by both ICC and GCC.