【问题标题】:gcc template resolution and namespacesgcc 模板解析和命名空间
【发布时间】:2014-05-02 10:50:37
【问题描述】:

警告,C++ 讨厌... 我在这里看到了这个问题略有不同的变化,这是我的看法

namespace space {
}

template <typename T> struct C
{
   void foo() {
      using namespace space;
      bar(t);
   }
   T t;
};

class A {
};

namespace space {

void bar(const A& a){
}

}

int main()
{
   C<A> c;
   c.foo();
   return 0;
}

如果 bar 不在命名空间内,则一切编译正常。放置命名空间会破坏 gcc 的编译。更有趣的是 - gcc 找到了函数,但就是不想使用它:

ConsoleApplication1.cpp: In instantiation of 'void C<T>::foo() [with T = A]':
ConsoleApplication1.cpp:26:10:   required from here
ConsoleApplication1.cpp:8:8: error: 'bar' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point
instantiation [-fpermissive]
   bar(t);
        ^
ConsoleApplication1.cpp:18:6: note: 'void space::bar(const A&)' declared here, later in the translation unit
 void bar(const A& a){

除了标准这样说之外,这种行为是否有任何合理的理由?有什么开关可以让 gcc 接受这个代码,因为它似乎没有技术问题,而且我不明白为什么从纯语言的角度来看,这样的代码不应该工作。

【问题讨论】:

  • 与模板参数不同,命名空间符号必须在调用时可见。因此无法编译代码。
  • @DieterLücking,但使用错误中显示的-fpermissive 开关除外。但这不是一个好主意。

标签: c++ templates gcc namespaces


【解决方案1】:

templates 不是宏。符号的名称查找在两种情况下完成:首先,您在其中编写了 template,第二次传递 only 参数相关的查找(ADL,或 Koeing Lookup),当您传递 @987654324 时@一个类型(当template“工厂”被实例化为一个实际的class或函数时)。

当函数与A在同一个命名空间时,通过ADL找到它。当它不是时,它不会是。因此,当您将 bar 函数移至 space::bar 时,不再使用 A 类型的参数找到 ADL。

这是有意为之,既可以防止您的 template 在不同位置表示完全不同的含义,也可以只允许非成员接口扩展类型在自己的命名空间中

要么使用特质类,将此类辅助函数粘贴在与类型相同的命名空间中,要么传入仿函数。

traits 类可能是最简单的。创建一个带有静态函数foo 的类(如果你想要一个默认实现:否则留空)。从您的template 拨打电话。专门针对A。现在您可以为您的 A 类型实现特殊行为——如果该函数在视图中,它甚至可以调用您的 space::bar 函数。

barA 放在同一个命名空间中是另一种解决方案,我发现它比traits 类更容易扩展。我的许多个人特征类最终都依赖于 ADL 查找,因为这让我可以在我定义 A 的位置旁边注入处理代码。 (特征类的使用让我也可以在std 中处理我的特征,在这里我不允许为生活在std 中的类型注入用于ADL 目的的函数(您可以将ADL 函数注入std,但只能对于您自己的类型))

函子解决方案是std::map 的工作原理——虽然它依赖于std::less&lt;T&gt;,而后者又依赖于operator&lt;,它需要一个函子,让您可以指定如何在this 中比较键类型 map.

【讨论】:

  • 最后,回答这个问题的要点:为什么?如果使用-fpermissive 规则完成名称查找,可能会出现这种奇怪行为的例子,也许这个答案会得到改善。
【解决方案2】:

除了标准这么说之外,这种行为有什么合理的理由吗?

无论是否理智,C++ 通常要求在使用前声明名称。 bar 使用前不要声明;具体来说,using namespace space 仅导入当时已声明的名称,而不是 bar

(如果你关心原因,那是因为 C++ 继承了半个世纪前的语言的声明规则,当时计算机还比较原始。这样的规则允许一次编译,所以编译器没有必须一直等待操作员将一叠打孔卡放回料斗中。或类似的事情;我不太确定当时的计算机是如何工作的。)

有什么开关可以让 gcc 接受这个代码

正如错误消息所说,-fpermissive。但是,如果您不遵守标准,您的代码将无法移植。

【讨论】:

  • 除了代码不可移植之外,使用-fpermissive 会改变很多,而不仅仅是在模板中查找名称。它还允许以前在标准 C++ 中允许的所有类型的非标准转换和怪异,失去一些类型安全性(我不知道细节;我不太确定 C++ 当时是如何工作的 :-)
  • 我的真实代码实际上更复杂,而且我已经在“空间”中有 void bar 重载,所以即使 -fpermissive 也无法拯救我,因为 gcc 将完全忽略其他 bar 重载。我很生气,即使使用 c++11,c++ 模板仍然不是很有用。我工作过的任何公司都很少使用模板,并且以最简单的形式......
【解决方案3】:

此错误与命名空间或模板完全无关,而是在声明之前使用函数的标准错误。就像你不能做的那样:

void caller() { callee(); }
void callee() {}

但必须改为:

void callee() {}     
void caller() { callee(); }

或者:

void callee();     
void caller() { callee(); }
void callee() {}

... 这同样适用于涉及模板和命名空间的更复杂的函数。请注意,如果您稍微重新排序,它就可以正常工作:

class A {
};

namespace space {
void bar(const A& a){
}
}

template <typename T> struct C
{
  void foo() {
    using namespace space;
    bar(t);
 }
 T t;
};


int main()
{
  C<A> c;
  c.foo();
  return 0;
 }

【讨论】:

  • 虽然您是正确的,原始代码给出的错误不是由于命名空间或模板引起的,但当bar 移动到全局命名空间时它编译的事实是 与命名空间和模板相关,因为在模板中查找依赖名称会考虑关联的命名空间,并且全局命名空间位于 A 的关联命名空间中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-03
  • 1970-01-01
  • 2014-11-14
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
相关资源
最近更新 更多