【问题标题】:Operator overloading, name resolution and namespaces运算符重载、名称解析和命名空间
【发布时间】:2016-07-12 13:02:00
【问题描述】:

我想对涉及 ADL、命名空间和运算符重载的令人费解的情况有所了解。

让 Foo 成为一个库,它在自己的命名空间中定义一个类 (Deriv),以及一个返回另一个类的模板化 operator *

namespace Foo {
    class Deriv {};
    class Another {};

    template <typename T>
    Another operator* ( T x, const Deriv& d ) { return Another();}
}

现在我在自己的库 Bar 中使用 Foo 的类,它定义了另一个 operator *,这次只用于 float

namespace Bar {
    typedef Foo::Deriv MyDeriv;
    MyDeriv operator* (float x, const MyDeriv& d) { return MyDeriv();}
}

我观察到编译器行为的差异取决于是否在 namespace Bar 内。

此函数 (Bar::f1) 使用 operator * 的第二个版本进行编译:

namespace Bar {
    void f1() {
        Bar::MyDeriv a;
        Bar::MyDeriv b = 3.f * a;
    }
} 

虽然命名空间 Bar (f2()) 之外的相同函数无法编译,因为编译器仅尝试使用 Foo::operator* 并且无法猜测它必须使用 Bar::operator*

void f2() {
    Bar::MyDeriv a; 
    Bar::MyDeriv b = 3.f * a; // Error : cannot convert Foo:Another to Bar::Myderiv
}

你可以在这里看到代码:http://ideone.com/pkPeOY

现在,如果 Foo::operator* 未被模板化并定义为 Foo::operator*(float, const Deriv&amp; d);,则 两个 函数无法编译并出现相同的错误(不明确的运算符重载),如下所示:http://ideone.com/wi1EWS

所以,面对这样的情况,这才是让我不解的地方

  • 模板化的情况下,编译f2时,编译器考虑使用Foo::operator*而不是Bar::operator*,而在非模板化在这种情况下,它考虑同时使用两者(并且由于模棱两可而拒绝更进一步)。 是什么让编译器的行为有所不同?

  • 我的库 Bar 的用户将位于 Bar:: 命名空间之外,但我希望使用 Bar::operator*,而不是 Foo::operator*。我考虑过明确地调用Bar::operator*(3.f,a),这很难看,或者在全局命名空间中插入我自己的运算符,我认为这是一个BadThing是否有我遗漏的选项,或者我做错了什么?

【问题讨论】:

    标签: c++ namespaces argument-dependent-lookup


    【解决方案1】:

    在模板化的情况下,编译f2时,编译器会考虑使用Foo::operator*而不是Bar::operator*,而在非模板化的情况下,它会考虑同时使用两者(并且由于歧义而拒绝更进一步) .是什么让编译器的行为有所不同?

    在这两种情况下,编译器都会考虑同时使用这两种情况,但在模板化 operator* 的情况下,调用不会有歧义,因为存在参数类型与参数完全匹配的非模板化函数(尝试将 3.f 替换为3.,您将看到找到模板版本)。通常:

    template <typename T>
    void g (T) { }
    
    void g (float) { }
    
    g(0.f); // Ok, the overload for float is preferred over the templated version
    

    我的库 Bar 的用户将位于 Bar:: 命名空间之外,但我希望使用 Bar::operator*,而不是 Foo::operator*。我考虑过明确地调用Bar::operator*(3.f,a),这很难看,或者在全局命名空间中插入我自己的运算符,我认为这是一件坏事。有没有我遗漏的选项,或者我做错了什么?

    不幸的是,ADL 找不到您的重载,因为 operator* 的唯一参数是 floatMyDeriv,它们在命名空间 Foo 中定义。一种可能的方法是从Foo::Deriv 继承:

    namespace Bar {
        struct MyDeriv: public Foo::Deriv {};
        MyDeriv operator* (float x, const MyDeriv& d) { return MyDeriv();}
    }
    

    另一种方法是在 Foo 命名空间内声明 operator* 的重载:

    namespace Bar {
        typedef Foo::Deriv MyDeriv;
    }
    
    namespace Foo {
        Bar::MyDeriv operator* (float x, const Bar::MyDeriv& d) { return Bar::MyDeriv(); }
    }
    

    【讨论】:

    • @GuyGreer 如果Foo::Deriv 中没有虚拟内容,我认为在大小方面不会有任何缺点(我猜一个好的编译器应该优化所有内容......)。跨度>
    • 嗯,出于某种原因,我认为派生类需要有自己的地址......我想我今天应该保持自己的状态,我不在我的游戏中。
    • 是的,很抱歉造成混乱。起初我以为是因为 Deriv 实际上是 CRTP 的一部分(因此是派生类),但它与问题无关,所以我删除了继承(但保留了名称,这可能会造成混淆)。
    猜你喜欢
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 2012-07-06
    • 2018-01-02
    • 2010-09-15
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多