Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

default template class argument confuses g++?

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>() );
like image 805
andreas buykx Avatar asked May 12 '09 07:05

andreas buykx


2 Answers

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>

like image 174
Richard Corden Avatar answered Oct 01 '22 09:10

Richard Corden


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

like image 37
Employed Russian Avatar answered Oct 01 '22 11:10

Employed Russian