views:

269

answers:

3

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
A: 

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.

Jack Lloyd
That's a waste of their time.
quant_dev
Wish I could upvote comments...
choudeshell
Unfortunately I can't do that in the actual code. I'll update the question.
Romulo A. Ceccon
If this really is valid code, it's not at all a waste of their time. Are you suggesting you know why it's invalid? Please tell us!
Jefromi
@quant_dev In what way? If it's valid code, which it seems to be, GCC not accepting it is a bug. If it's not valid code, GCC certainly could provide a more informative error message. Either way, GCC has a problem here.
Jack Lloyd
I think one should file a bug report when one is *certain* that valid code is not being compiled.You may file a bug report about an unhelpful error message, though.
quant_dev
After the edit, this discussion doesn't make sense anymore :)
quant_dev
+7  A: 

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.

stijn
This works. See also: http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/keyword_template_qualifier.htm
quant_dev
+5  A: 

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.

Richard Corden
+1, one of the more thorough explanation I ever got about this 'quirk'. If only the compiler could be 'smarter' though as always it's much easier said than done... especially considering than it could be a 'value' instead of int ;)
Matthieu M.