V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wutiantong
V2EX  ›  C

放在 template class 里面的 friend binary-operator 和 放在外面的相同实现的 operator 在传参数上有什么差异呢?

  •  
  •   wutiantong · 2018-11-26 18:52:20 +08:00 · 2181 次点击
    这是一个创建于 2221 天前的主题,其中的信息可能已经有所发展或是发生改变。

    请看下面的代码:

    template<typename T>
    struct A
    {
        T i;
        // friend bool operator<(A const& l, A const& r) { return l.i < r.i; }
    };
    template<typename T> bool operator<(A<T> const& l, A<T> const& r) { return l.i < r.i; }
    
    struct B 
    {
        float f;
        template<typename T> operator A<T>() const { return A<T>{static_cast<T>(f)}; }
    };
    int main()
    {
        bool a = A<int>{3} < A<int>{4};
        bool b = A<int>{3} < B{5.4f};
    }
    

    通过 friend operator 实现的比较可以支持 A 与 B 的比较,而在外面直接实现 operator 则会导致 A 与 B 的比较编译出错,参考:
    https://godbolt.org/z/xsBlnA

    这是为啥呢?如果不借助 friend 如何才能使得 A 与 B 可以正常比较?

    11 条回复    2018-11-27 13:22:46 +08:00
    429839446
        1
    429839446  
       2018-11-26 22:47:34 +08:00
    隐式转换和模板实例化以及函数重载决议的冲突,有点忘了。
    429839446
        2
    429839446  
       2018-11-26 22:47:56 +08:00
    请楼主自行看书或者 cpp ref
    wutiantong
        3
    wutiantong  
    OP
       2018-11-26 22:59:28 +08:00 via iPhone
    @429839446 你之前遇到过这个问题么?
    feverzsj
        4
    feverzsj  
       2018-11-27 00:14:45 +08:00   ❤️ 2
    调用模板函数,首先要进行模板推演,编译器还不会聪明到同时做隐式类型转换,而 friend 那个不是模板函数,因为在你实例化 A<int>时,这个函数就已经确定了,编译器可以直接进行隐式类型转换
    wutiantong
        5
    wutiantong  
    OP
       2018-11-27 00:19:39 +08:00
    @feverzsj 有道理
    wutiantong
        6
    wutiantong  
    OP
       2018-11-27 00:38:49 +08:00
    @feverzsj 这是否意味着将此类 operator 或 function 声明为 friend 放在 template class 内部(总)是一种更好的实践?
    GeruzoniAnsasu
        7
    GeruzoniAnsasu  
       2018-11-27 10:37:46 +08:00 via Android
    @wutiantong 避免隐式转换才是最佳实践
    wutiantong
        8
    wutiantong  
    OP
       2018-11-27 10:58:51 +08:00
    @GeruzoniAnsasu 不认同这个说法,你在哪看到的?
    GeruzoniAnsasu
        9
    GeruzoniAnsasu  
       2018-11-27 12:36:30 +08:00
    @wutiantong 还真说不上来为什么,但感觉也说不上来隐式转换有什么必要性,从 A 隐式转换为 B 这个场景其实一般真正的目的只是想提供统一的接口,这完全可以通过调整抽象层次解决,A 转 B 的写法也必定是侵入式的,一定程度上破坏了封装,除非 B 是 A 的派生类型。但派生类自带多态,类型转换真的会用到吗

    就比如这个比较

    https://gist.github.com/pnck/8875c927da7aaa90e6da72a1a1245616

    写成这样是不是会好一点?

    注意使用接口类的 value()是多态特性,并不是隐式转换
    wutiantong
        10
    wutiantong  
    OP
       2018-11-27 13:09:20 +08:00
    @GeruzoniAnsasu

    1. STL 里面就有很常见的 std::basic_ios,std::shared_ptr 到 bool 的转换。

    2. “ A 转 B 的写法也必定是侵入式的,一定程度上破坏了封装,除非 B 是 A 的派生类型” ,从侵入的角度来说,在 B 里面定义 operator A() 跟 把 B 定义为 A 的子类 是没有区别的,你再仔细想想?

    3. 通过继承和多态引入的语义以及性能负担与定义隐式转换不可同日而语,它们有不同的使用场景。

    4. 代码中的 UNIFORM_VALUE_TYPE 可以用 std::common_type 来改善。

    5. 代码中 comparable 这个接口类实现了一个统一的比较接口,但它实际上并没有做任何具体的比较(而是委托给了两个 value 的 common_type 的比较函数),这跟我所要做的事情本质上并无关联。
    wutiantong
        11
    wutiantong  
    OP
       2018-11-27 13:22:46 +08:00
    @GeruzoniAnsasu 上文第 2 点,我可能对“除非 B 是 A 的派生类型”这里有些误解,但我知道你想强调的是“隐式转换导致的耦合”。如果是把两个关联很弱的类硬拉凑到一起做个隐式转换,这的确会产生额外的耦合。但是“额外的耦合”这种问题本质上并不应归咎于一个无辜的语法特性,而应归咎于设计者的缺乏经验及不慎。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5600 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 08:16 · PVG 16:16 · LAX 00:16 · JFK 03:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.