9

昨天我遇到了一个 g++ (3.4.6) 编译器问题,我使用 Intel (9.0) 编译器编译的代码没有问题。这是一个显示发生了什么的代码片段:

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

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

g++ 编译器错误是:

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)'

显然,以这种方式编写时不接受默认参数,并且编译器假定指定了新的函数参数而不是第二个模板参数,然后它需要一个默认值,因为该stuff参数有一个。我可以通过创建 typedef 来帮助编译器,然后一切都编译得很好:

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

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

所以我可以解决我的问题,但我不明白发生了什么。我是否在这里错过了 C++(模板?)语言功能,我做错了什么,还是 g++ 编译器不接受第一段代码是错误的?

注意顺便说一句,这也编译...

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

void method ( Foo<int,int> const& stuff = Foo<int,int>() );
4

4 回答 4

12

我不太确定这是 g++ 中的错误(从 4.2.4 版开始)。代码现在在 g++ 4.4 中传递(参见下面的更新)。为了让此代码为其他版本的编译器编译,您可以在默认参数周围添加一组括号:

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

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

IMO,这些括号是必要的,因为还有一个额外的要求,即默认参数可以引用类的成员,该成员可以稍后在类主体中声明:

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

上面的代码是合法的,当解析“方法”的声明时,还没有看到成员“j”。因此,编译器只能使用语法检查来解析默认参数(即匹配括号和逗号)。当 g++ 解析您的原始声明时,它实际看到的是以下内容:

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

添加额外的括号集可确保正确处理默认参数。

以下案例显示了 g++ 成功但 Comeau 仍然生成语法错误的示例:

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;
};

编辑:

回应评论:“你也可以在那里有一个带有多个参数的函数调用”,这不会导致问题的原因是函数调用中的逗号被括号括起来:

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

因此,可以仅使用表达式的语法对其进行解析。在 C++ 中,所有的 '(' 必须与 ')' 匹配,所以这很容易解析。这里问题的原因是 '<' 不需要匹配,因为它在 C++ 中被重载,因此可以是小于运算符或模板参数列表的开头。以下示例显示了在默认参数中使用 '<' 并暗示小于运算符的位置:

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;
};

上面的“Foo<10”是对“X”中定义的“operator<”的调用,而不是模板参数列表的开始。同样,Comeau 在上述代码上生成语法错误,而 g++(包括 3.2.3)会正确解析它。

仅供参考,适当的参考是 8.3.6/5 中的注释:

[注意:在成员函数声明中,默认参数表达式中的名称按照 3.4.1 中的描述进行查找...

然后在 3.4.1/8

在函数的 declaratorid29 之后的类 X 的成员函数 (9.3) 的定义中使用的名称应以下列方式之一声明:

...

— 应为 X 类的成员或 X (10.2) 的基类的成员,或

这里的这个项目符号是强制编译器“延迟”查找默认参数的含义的部分,直到所有类成员都被声明。

<更新>

正如“Employed Russian”所指出的,g++ 4.4 现在能够解析所有这些示例。但是,在 C++ 标准委员会解决DR之前,我还没有准备好将其称为“错误”。我相信需要长期的额外括号来确保对其他编译器/工具(甚至可能是 g++ 的未来版本)的可移植性。

根据我的经验,C++ 标准并没有规定编译器供应商都应该使用相同的解析器技术,他们也不能期望所有技术都同样强大。因此,解析需求通常不需要供应商执行超人的壮举。为了说明这一点,请考虑以下两个示例:

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

如果'T'是依赖的,那么给定每个上下文'TYPE'必须是一个类型名,但是标准仍然需要类型名关键字。这些示例是明确的并且只能表示一件事,但是标准(为了允许所有解析器技术)仍然需要typename关键字。

只要额外的括号允许代码解析,DR 可能会以这样一种方式解决,即无法解析这些示例的编译器仍将是“符合标准的”。

</更新>

于 2009-05-12T08:38:08.670 回答
4

这是 gcc 中的一个已知错误。它已在 gcc-4.4 中修复,它可以很好地编译原始源代码。

于 2009-05-12T19:50:20.117 回答
2

看起来像一个编译器错误。我在 IBM 的 xlC 编译器 V7.0(我发现它比 gcc 更符合标准)上进行了尝试,它编译得很好。

于 2009-05-12T07:55:37.210 回答
-2
template <bool> class A {};
typedef A<static_cast<bool>(1>0)> B;//buggy
int main() { B b; }
于 2009-08-15T15:08:29.097 回答