代码之家  ›  专栏  ›  技术社区  ›  alfC

友元二元算子的消歧

  •  3
  • alfC  · 技术社区  · 6 年前

    operator+ 举个例子)。

    struct B{};
    
    template<class>
    struct A{
        template<class BB>
        void operator+(BB const&) const{std::cout<<"member"<<std::endl;}
        template<class BB>
        friend void operator+(BB const&, A const&){std::cout<<"friend"<<std::endl;}
    };
    

    我可以用两种不同的类型来调用这个二进制运算符:

    A<int> a;
    B b;
    a + b; // member
    b + a; // friend
    

    当我尝试使用 A 两边( a + a )很多奇怪的事情发生了。三个编译器对同一代码给出不同的答案。

    一些背景:我不想定义 void operator+(A const&) template<class BB, class AA> friend void operator(BB const&, AA const&) . 因为自从 A

    继续原始代码:

    奇怪的事情#1:

    a + a; // prints friend in gcc
    

    我希望成员优先, 有没有办法让成员优先?

    奇怪的事情2: 在clang中,此代码不编译:

    a + a; // use of overload is ambiguous
    

    如果我试图在参数上更加贪婪,例如应用一些优化,我可以使用转发引用:

    struct A{
        template<class BB>
        void operator+(BB&&) const{std::cout<<"member"<<std::endl;}
        template<class BB>
        friend void operator+(BB&&, A const&){std::cout<<"friend"<<std::endl;}
    };
    

    奇怪的事情3:

    a + a; // print "friend", but gives "warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:"
    

    但仍然编译, 就像案例1一样,我希望更喜欢成员函数,但这里它更喜欢朋友函数 发出警告。

    怪事四:

    a + a; // error: use of overloaded operator '+' is ambiguous (with operand types 'A' and 'A')
    

    这再次指出了gcc和clang之间的不一致性,在这个案例中谁是对的?

    总而言之,我正在努力使这段代码始终如一地工作。我真的希望这个函数是注入的朋友函数(不是免费的朋友函数)。我不想用相等的非模板参数定义函数,因为不同的实例化将产生相同函数的重复声明。


    #include<iostream>
    using std::cout;
    struct B{};
    
    template<class>
    struct A{
        template<class BB>
        void operator+(BB const& /*or BB&&*/) const{cout<<"member\n";}
        template<class BB>
        friend void operator+(BB const& /*or BB const&*/, A const&){cout<<"friend\n";}
    };
    
    int main(){
        A<int> a;      //previos version of the question had a typo here: A a;
        B b;
        a + b; // calls member
        b + a; // class friend
        a + a; // surprising result (friend) or warning in gcc, hard error in clang, MSVC gives `member` (see below)
    
        A<double> a2; // just to instantiate another template
    }
    

    注意:我正在使用 clang version 6.0.1 g++ (GCC) 8.1.1 20180712 . 根据Francis Cugler MSVS 2017,CE给出了一个不同的行为。


    我找到了一个解决方法,可以做正确的事情(为 a+a 对于clang和gcc(对于MSV?),但对于boiler plate和人工基类需要大量的数据:

    template<class T>
    struct A_base{
        template<class BB>
        friend void operator+(BB const&, A_base<T> const&){std::cout<<"friend"<<std::endl;}
    };
    
    template<class T>
    struct A : A_base<T>{
        template<class BB>
        void operator+(BB const&) const{std::cout<<"member"<<std::endl;}
    };
    

    但是,如果我替换它,它仍然会给出一个模棱两可的调用 BB const& BB&& .

    3 回复  |  直到 4 年前
        1
  •  2
  •   T.C. Yksisarvinen    6 年前
        2
  •  1
  •   alfC    6 年前

    我在Visual Studio 2017 CE中运行了您的代码:

    int main(){
        A<int> a;
        B b;
        a + b; // calls member
        b + a; // class friend
        a + a; // surprising result or warning in gcc, hard error in clang
    }
    

    visualstudio编译没有错误,运行成功,没有警告,程序返回时退出,代码为 (0) 使用此输出:

    member
    friend
    member
    

    我用float,double,char试过了,得到了同样的结果。

    作为一个额外的测试,我在上面的模板类之后添加了这个:

    /* your code here */
    
    struct C {};
    
    int main() {
        A<C> a;
        B b;
        a + b;
        b + a;
        a + a;
    
        return 0;
    }
    

    结果还是一样的。


    Strange thing #1, #2, #3, #4: gcc & clang

    a + a member 对于输出,成员的优先级高于友元重载。至于运算符优先的事实,我不知道是否 GCC Clang Visual Studio 由于每个编译器的工作方式不同,我对它们并不熟悉,但对于语言本身,您的编译器不知道该怎么处理 A::+() 当它不知道 <type> A<int>::+() A<char>::+() A<C>::+() ...

        3
  •  1
  •   alfC    6 年前

    template<
      class BB,
      std::enable_if<
        !std::is_convertible<B const&, A>::value, int>::type = 0>
    friend void operator+(BB const&, A const&){std::cout<<"friend"<<std::endl;}
    

    注意:您需要使用 std::enable_if 作为一种类型,使它的功能 如果SFINAE不解决,就永远不会解决。

    template <typename BB = A>
    void operator++(BB const&) const {/*...*/}
    

    只有在向类提供其他opterator++时,这才是真正有用的,但值得注意的是。