views:

1580

answers:

4

Hi all,

Yesterday I ran into a g++ (3.4.6) compiler problem for code that I have been compiling without a problem using the Intel (9.0) compiler. Here's a code snippet that shows what happened:

template<typename A, typename B>
class Foo { };

struct Bar {
   void method ( Foo<int,int> const& stuff = Foo<int,int>() );
};

The g++ compiler error is:

foo.cpp:5: error: expected `,' or `...' before '>' token
foo.cpp:5: error: wrong number of template arguments (1, should be 2)
foo.cpp:2: error: provided for `template<class A, class B> struct Foo'
foo.cpp:5: error: default argument missing for parameter 2 of `void Bar::method(const Foo<int, int>&, int)'

Apparently, the default argument is not accepted when written this way, and the compiler assumes that instead of the second template argument a new function argument is specified, for which it then expects a default value because the stuff argument has one. I can help the compiler by creating a typedef, and then everything compiles fine:

template<typename A, typename B>
class Foo { };

struct Bar {
   typedef Foo<int,int> FooType;
   void method ( FooType const& stuff = FooType() );
};

So I can solve my problem, but I don't understand what is going on. Do I miss a C++ (template?) language feature here and am I doing something wrong, or is the g++ compiler wrong in not accepting the first piece of code?

Note BTW that this also compiles ...

template<typename A, typename B>
class Foo { };

void method ( Foo<int,int> const& stuff = Foo<int,int>() );
+2  A: 

Looks like a compiler bug. I tried it on IBM's xlC compiler V7.0 (which I've found to be more standard-conforming than gcc) and it compiles okay.

Ari
I think you will find the bug is in xlC. The above code is ambiguous and could be interpreted in two ways. xlC has decided to enforce one interpretation while gcc forces the developer to clarify.
Martin York
+6  A: 

I am not so sure that this is a bug in g++ (since version 4.2.4). The code now passes in g++ 4.4 (see UPDATE below). In order to have this code compile for other versions of compilers you can add a set of parenthesis around the default argument:

template<typename A, typename B>
class Foo { };

struct Bar {
  void method ( Foo<int,int> const& stuff = ( Foo<int,int>() ) );
};

IMO, these parenthesis are necessary as there is an additional requirement that the default argument can refer to a member of the class that may be declared later in the class body:

struct Bar {
  void method ( int i = j);  // 'j' not declared yet
  static const int j = 0;
};

The above code is legal, and when the declaration for 'method' is being parsed the member 'j' has not yet been seen. The compiler therefore can only parse the default argument using syntax checking only, (ie. matching parenthesis and commas). When g++ is parsing your original declaration, what it is actually seeing is the following:

void method ( Foo<int,int> const& stuff = Foo<int // Arg 1 with dflt.
              , int>() );                         // Arg 2 - syntax error

Adding the extra set of parenthesis ensures that the default argument is handled correctly.

The following case shows an example where g++ succeeds but Comeau still generates a syntax error:

template<int J, int K>
class Foo { };

struct Bar {
  void method ( Foo<0, 0> const & i = ( Foo<j, k> () ) );
  static const int j = 0;
  static const int k = 0;
};

EDIT:

In response to the comment: "You could just as well have a function call with multiple arguments there", the reason that this does not cause a problem is that the comma's inside the function call are surrounded in parenthesis:

int foo (int, int, int);
struct Bar {
  void method ( int j =
                    foo (0, 0, 0) ); // Comma's here are inside ( )
};

It is possible therefore, to parse this using the syntax of the expression only. In C++, all '(' must be matched with ')' and so this is easy to parse. The reason for the problem here is that '<' does not need to be matched, since it is overloaded in C++ and so can be the less than operator or the start of a template argument list. The following example shows where '<' is used in the default argument and implies the less than operator:

template<int I, int J>
class Foo { };

struct Bar {
  template <typename T> struct Y { };

  void method ( ::Foo<0,0> const& stuff = Foo<10 , Y < int >  = Y<int>() );

  struct X {
    ::Foo<0, 0> operator< (int);
  };

  static X Foo;
};

The above "Foo<10" is a call to the "operator<" defined in 'X', not the start of the template argument list. Again, Comeau generates syntax errors on the above code and g++ (including 3.2.3) parse this correctly.

FYI, the appropriate references are a note in 8.3.6/5:

[Note: in member function declarations, names in default argument expressions are looked up as described in 3.4.1...

and then in 3.4.1/8

A name used in the definition of a member function (9.3) of class X following the function’s declaratorid29) shall be declared in one of the following ways:

...

— shall be a member of class X or be a member of a base class of X (10.2), or

This bullet here, is the part that forces the compiler to "delay" lookup for the meaning of the default argument until all of the class members have been declared.

<UPDATE>

As pointed out by "Employed Russian", g++ 4.4 is now able to parse all of these examples. However, until the DR has been addressed by the C++ standards committee I am not yet ready to call this a "bug". I believe that long term extra parenthesis will be required to ensure portability to other compilers/tools (and maybe even future versions of g++).

It has been my experience that the C++ standard does not dictate that compiler vendors should all use the same parser technology and they also cannot expect that all technologies are equally powerful. As a result, parsing requirements normally don't require that vendors perform superhuman feats. To illustrate this consider the following two examples:

typedef T::TYPE TYPE;
T::TYPE t;

If 'T' is dependent, then given each context 'TYPE' must be a typename, however the standard still requires the typename keyword. These examples are unambiguous and can only mean one thing, however the standard (in order to allow for all parser technologies) still requires the typename keyword.

It's possible that the DR may be addressed in such a way that a compiler which fails to parse these examples will still be "standard conforming" as long as extra parenthesis allows the code to parse.

</UPDATE>

Richard Corden
Well, that was one of the first things I tried with my gcc (g++ (GCC) 3.2.3 20030502 (Red Hat Linux 3.2.3-49)) and it gave a syntax error.
Ari
Also, the compiler should be able to recognize that the entire expression is a legal identifier and not stop at the first comma. You could just as well have a function call with multiple arguments there. You wouldn't expect that to fail too, would you? I'd have to say it's a bug.
Ari
@Ari: Comment 1: The code passes with g++ 4.2.4 and 4.2.3, so yes it *was* a bug in gcc 3.2.3 but it has been fixed.
Richard Corden
Finally, in your last code snippet, are you sure Comeau reports a syntax error and not some other type of error? I would assume the problem is that Comeau is not willing to treat j and k as "constant enough" to serve as template parameter values. This may well be what the standard requires, making g++ again non-conforming. (Of course, my g++ doesn't compile that either.)
Ari
@Ari. I think your wrong there. If you declare those variables above the default argument then Comeau accepts the code. I know that it's hard to believe, I am always left in awe at just how good the EDG front end (used by Comeau) is in terms of parsing standard C++, but it's not infallible.
Richard Corden
Hmmm... Okay, now it looks more like a gray corner in the language.
Ari
nice answer, good reasoning about gcc's failure. +1 indeed
Johannes Schaub - litb
The answer is wrong.This is a known bug in gcc: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57It has been fixed in gcc-4.4, which compiles the original source just fine.
Employed Russian
@Employed, they are saying themselves that it's not a bug yet. That's why they suspended the report, until a statement of the committee has been posted on the DR.
Johannes Schaub - litb
However, in the defect report, i don't see a real defect (apart from the calls to static functions, where values depend on each other) - but rather complaining about implementation difficulties (like, looking up names already before class-definition is complete). To me, it looks like the way gcc now behaves is the standard compliant way regarding the original example by @andreas.
Johannes Schaub - litb
+2  A: 

This is a known bug in gcc. It has been fixed in gcc-4.4, which compiles the original source just fine.

Employed Russian
Strictly, you can only call it a bug iff the standard dictates that this is the correct behaviour. As the standard doesn't say either way what is the correct behaviour here, you could equally argue that gcc is wrong now and was correct to reject the code originally.
Richard Corden
A: 
template <bool> class A {};
typedef A<static_cast<bool>(1>0)> B;//buggy
int main() { B b; }
none