简体   繁体   English

仅作为成员或朋友工作的嵌套结构的运算符重载 function

[英]Operator overloading for nested struct only working as member or friend function

This C++ code compiles and runs perfectly, as I expect:正如我所料,这段 C++ 代码可以完美地编译和运行:

template <typename T>  struct S { T *p; };

template <typename T>
bool operator == (S<T> &a, S<T> &b) { return a.p == b.p; }

int main () { int i;  S<int> a = {&i}, b = {&i};  return a == b; }

However, if I try to do the same with the inner struct of an outer struct...但是,如果我尝试对外部结构的内部结构执行相同的操作...

template <typename T>  struct O {  struct I {T *p;};  };

template <typename T>
bool operator == (O<T>::I &a, O<T>::I &b) { return a.p == b.p; }

int main () { int i;  O<int>::I a = {&i}, b = {&i};  return a == b; }

... then it doesn't compile anymore (gcc version 8.3.0, Debian GNU/Linux 10): ...然后它不再编译(gcc 版本 8.3.0、Debian GNU/Linux 10):

1.cpp:4:25: error: declaration of ‘operator==’ as non-function
 bool operator == (O<T>::I &a, O<T>::I &b) { return a.p == b.p; }
                         ^
[...]

Why is it so?为什么会这样? I also do not understand the above error message.我也不明白上面的错误信息。

Note that I'm aware that I can make it work by defining the operator as a member function of the inner struct:请注意,我知道我可以通过 将运算符定义为内部结构的成员 function来使其工作:

template <typename T>
struct O2 {
           struct I2 {
                      T *p;

                      bool operator == (I2 &b) { return p == b.p; }
                     };
          };

int main () { int i;  O2<int>::I2 a = {&i}, b = {&i};  return a == b; }

However, if somehow possible, I'd rather use the non-member function version, because I find it more symmetric and therefore clearer.但是,如果可能的话,我宁愿使用非成员 function 版本,因为我发现它更对称,因此更清晰。

Also, partly by trial and error, I found that the following symmetric version works...此外,部分通过反复试验,我发现以下对称版本有效......

template <typename T>
struct O3 {
           struct I3 { T *p; };

           friend bool operator == (I3 &a, I3 &b) { return a.p == b.p; }
          };

int main () { int i;  O3<int>::I3 a = {&i}, b = {&i};  return a == b; }

... but I do not really understand what is happening above. ...但我真的不明白上面发生了什么。 First, given that a friend declaration "grants a function or another class access to private and protected members of the class where the friend declaration appears", I do not understand how it helps in the code above, given that we're always dealing with structs and therefore with public members.首先,鉴于朋友声明“授予 function 或另一个 class 访问出现朋友声明的 class 的私有和受保护成员”,我不明白它在上面的代码中有何帮助,因为我们总是在处理结构,因此具有公共成员。

Second, if I remove the friend specifier, then it doesn't compile anymore.其次,如果我删除friend元说明符,那么它就不会再编译了。 Also, the [...] operator== [...] must have exactly one argument error message makes me think that in this case the compiler expects me to define a member function operator== whose left operand is O3 , not I3 .此外, [...] operator== [...] must have exactly one argument错误消息让我认为在这种情况下,编译器希望我定义一个成员 function operator==其左操作数是O3 ,而不是I3 . Apparently, however, the friend specifier changes this situation;然而,显然, friend说明符改变了这种情况; why is it so?为什么会这样?

First, the compiler gets confused by missing typename .首先,编译器因缺少typename而感到困惑。 The error message really is confusing and can be silenced via:错误消息确实令人困惑,可以通过以下方式消除:

template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { 
    return a.p == b.p; 
}

Now the actual problem gets more apparent:现在实际问题变得更加明显:

int main () { 
    int i;  
    O<int>::I a = {&i}, b = {&i};  
    return a == b; 
}

results in the error:导致错误:

<source>: In function 'int main()':
<source>:15:14: error: no match for 'operator==' (operand types are 'O<int>::I' and 'O<int>::I')
   15 |     return a == b;
      |            ~ ^~ ~
      |            |    |
      |            |    I<[...]>
      |            I<[...]>
<source>:8:6: note: candidate: 'template<class T> bool operator==(typename O<T>::I&, typename O<T>::I&)'
    8 | bool operator == (typename O<T>::I &a, typename O<T>::I &b) {
      |      ^~~~~~~~
<source>:8:6: note:   template argument deduction/substitution failed:
<source>:15:17: note:   couldn't deduce template parameter 'T'
   15 |     return a == b;
      |                 ^

It is not possible to deduce T from a == b , (@dfribs words)不可能从a == b推导出T ,(@dfribs 的话)

because T is in a non-deduced context in both of the function parameters of the operator function template;因为T在运算符 function 模板的两个 function 参数中都处于非推导上下文中; T cannot be deduced from O<T>::I& , meaning T cannot be deduced from any of the arguments to the call (and function template argument deduction subsequently fails). T不能从O<T>::I&推导出来,这意味着T不能从调用的任何 arguments 推导出来(并且 function 模板参数推导随后失败)。

Sloppy speaking, because O<S>::I could be the same as O<T>::I , even if S != T .草率地说,因为O<S>::I可能与O<T>::I相同,即使S != T也是如此。

When the operator is declared as member then there is only one candidate to compare a O<T>::I with another (because the operator itself is not a template, ie no deduction needed).当运算符被声明为成员时,只有一个候选者可以将O<T>::I与另一个进行比较(因为运算符本身不是模板,即不需要推导)。


If you want to implement the operator as non member I would suggest to not define I inside O :如果您想将运算符实现为非成员,我建议不要在O中定义I

template <typename T> 
struct I_impl {
    T *p;
}; 

template <typename T>
bool operator == (I_impl<T> &a,I_impl<T> &b) {
     return a.p == b.p; 
}

template <typename T>  
struct O {  
    using I = I_impl<T>;  
};

int main () { 
    int i;  
    O<int>::I a = {&i}, b = {&i};  
    return a == b; 
}

Your confusion about friend is somewhat unrelated to operator overloading.您对friend的困惑与运算符重载有些无关。 Consider:考虑:

#include <iostream>

void bar();

struct foo {
    friend void bar(){ std::cout << "1";}
    void bar(){ std::cout << "2";}
};

int main () { 
    bar();
    foo{}.bar();
}

Output: Output:

12

We have two definitions for a bar in foo .我们对foo中的bar有两个定义。 friend void bar(){ std::cout << "1";} declares the free function ::bar (already declared in global scope) as a friend of foo and defines it. friend void bar(){ std::cout << "1";}将自由 function ::bar (已在全局范围内声明)声明为foo的友元并定义它。 void bar(){ std::cout << "2";} declares (and defines) a member of foo called bar : foo::bar . void bar(){ std::cout << "2";}声明(并定义)一个名为barfoo成员: foo::bar

Back to operator== , consider that a == b is a shorther way of writing either回到operator== ,考虑a == b是一种更短的写作方式

a.operator==(b);  // member == 

or要么

operator==(a,b);  // non member ==

Member methods get the this pointer as implicit parameter passed, free functions not.成员方法获取this指针作为传递的隐式参数,自由函数则不然。 Thats why operator== must take exactly one parameter as member and exactly two as free function and this is wrong:这就是为什么operator==必须只将一个参数作为成员,将两个参数作为自由 function,这是错误的:

struct wrong {
    bool operator==( wrong a, wrong b);
};

while this is correct:虽然这是正确的:

struct correct {
    bool operator==(wrong a);
};
struct correct_friend {
    friend operator==(wrong a,wrong b);
};

Compiling C++ does not require compilers to solve the halting problem.编译C++不需要编译器解决停机问题。

template <typename T>  struct O {  struct I {T *p;};  };

template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }

going from O<int>::I to int requires, in the general case, that the compiler solve the halting problem.在一般情况下,从O<int>::Iint需要编译器解决暂停问题。 To demonstrate why:为了证明为什么:

template <typename T>  struct O {  struct I {T *p;};  };
template <>  struct O<double> {  using I=O<int>::I;  };
template <>  struct O<char> {  using I=std::string;  };

template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }

now, O<int>::I can be named O<double>::I .现在, O<int>::I可以命名为O<double>::I You can actually make the map from O<T>::I to T require inverting an arbitrary turing-complete function, as template specialization is turing-complete.您实际上可以使 map 从O<T>::IT需要反转任意图灵完备 function,因为模板特化是图灵完备的。

Rather than carving out a region of invertable dependent type maps, C++ simply says "do not invert dependent types" when doing template pattern matching of arguments. C++ 在进行 arguments 的模板模式匹配时,并没有划出一个可反转的依赖类型映射区域,而是简单地说“不要反转依赖类型”。

So所以

template <typename T>
bool operator == (typename O<T>::I &a, typename O<T>::I &b) { return a.p == b.p; }

will never deduce T .永远不会推断出T


Now you can make this work, but it requires that you define the inverse mapping without relying on template type deduction.现在您可以完成这项工作,但它要求您定义逆向映射而不依赖于模板类型推导。

By far the easiest is to make I be defined outside O .到目前为止,最简单的方法是让IO之外定义。 Failing that, you need to define a way to find O<T> given O<T>::I , like:如果做不到这一点,您需要定义一种方法来查找O<T>给定O<T>::I ,例如:

template <typename T>  struct O {
  struct I {using Outer=O<T>;T *p;};
};
template<class Inner>
using Outer=typename Inner::Outer;
template<class X>
struct Type0;
template<template<class...>class Z, class T0, class...Ts>
struct Type0<Z<T0,Ts...>>{ using type=T0; };
template<class X>
using Type0_t=typename Type0<X>::type;

we can then那么我们可以

template <class I> requires (std::is_same_v<I, typename O<Type0_t<Outer<Inner>>>::I>)
bool operator == (Inner const &a, Inner const &b) { return a.p == b.p; }

and your code works.并且您的代码有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM