简体   繁体   English

operator== 用于可变参数模板 class 中的类

[英]operator== for classes inside a variadic template class

I have the following class structure:我有以下 class 结构:

template <typename...>
class SomeClass {
public:
    class Foo {  };
    class Bar {  };
};

I need to define operator== for SomeClass<Ts...>::Foo and SomeClass<Ts...>::Bar and I need to make it a friend of both Foo and Bar .我需要为SomeClass<Ts...>::FooSomeClass<Ts...>::Bar定义operator==并且我需要让它成为FooBar的朋友。 The closest I got this to working is the following:我最接近工作的是以下内容:

template <typename...>
class SomeClass {
public:
    class Bar;
    class Foo {
        
        friend bool operator==(const Foo&, const Bar&) { 
            return true;
        }
    };
    class Bar { 
        
        friend bool operator==(const Foo&, const Bar&);
    };
};

Then I do:然后我做:

SomeClass<int, double>::Foo foo;
SomeClass<int, double>::Bar bar;
foo == bar;

This compiles and works fine except for the fact that gcc gives me the warning:除了 gcc 给我警告之外,这编译和工作正常:

warning: friend declaration `bool operator==(const SomeClass<Args>::Foo&, const SomeClass<Args>::Bar&)` declares a non-template function
note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)

I kind of understand why this happens (operator== indeed depends on the template parameters of SomeClass), but how do I get rid of it?我有点理解为什么会发生这种情况(operator== 确实取决于 SomeClass 的模板参数),但是我该如何摆脱它呢? Adding <> to the second friend declaration, as suggested, only breaks compilation: the friending is no longer recognized and the compiler complaints about access to a private member.正如建议的那样,将 <> 添加到第二个朋友声明中只会破坏编译:不再识别友谊并且编译器抱怨访问私有成员。

I tried to modify the code according to the template friend declaration guide , but that only made things worse:我试图根据模板朋友声明指南修改代码,但这只会让事情变得更糟:

template <typename...>
class SomeClass;

template <typename... Args>
bool operator==(const typename SomeClass<Args...>::Foo&, 
                const typename SomeClass<Args...>::Bar&);

template <typename... Args>
class SomeClass {
public:
    class Bar;
    class Foo {
        
        friend bool operator==<Args...>(const Foo&, const Bar&);
    };
    class Bar { 
        
        friend bool operator==<Args...>(const Foo&, const Bar&);
    };
};

template <typename... Args>
bool operator==(const typename SomeClass<Args...>::Foo&, 
                const typename SomeClass<Args...>::Bar&) {
    return true;
}

Now the weirdest thing happens when I call foo == bar :现在最奇怪的事情发生在我调用foo == bar时:

error: no match for ‘operator==’ (operand types are ‘SomeClass<int, double>::Foo’ and ‘SomeClass<int, double>::Bar’)
note: candidate: ‘bool operator==(const typename SomeClass<Args ...>::Foo&, const typename SomeClass<Args ...>::Bar&) [with Args = {}; typename SomeClass<Args ...>::Foo = SomeClass<>::Foo; typename SomeClass<Args ...>::Bar = SomeClass<>::Bar]’ (reversed)
note: no known conversion for argument 1 from ‘SomeClass<int, double>::Bar’ to ‘const SomeClass<>::Foo&’

Ie for some weird reason it tries to call the template specification with an empty arguments list and fails.即由于某些奇怪的原因,它尝试使用空的 arguments 列表调用模板规范并失败。 Changing the operator type (to operator+) doesn't help (my idea was, this had something to do with C++20 new rules for comparison operators, but no).更改运算符类型(到 operator+)并没有帮助(我的想法是,这与 C++20 比较运算符的新规则有关,但不是)。

So the questions I have are:所以我的问题是:

  1. What's going on?这是怎么回事? I'm kind of confused, especially by the second part: why would the compiler try to call the operator for an empty parameter pack?我有点困惑,尤其是第二部分:为什么编译器会尝试为空参数包调用运算符?
  2. How do I avoid the warning in the first solution?如何避免第一个解决方案中的警告?
  3. How do I define the operator outside of the class definition properly?如何正确定义 class 定义之外的运算符?

I got this solution with operator== 's definition outside the class.我在 class 之外使用operator==的定义得到了这个解决方案。 There is no warning now, but I had to remove the friend qualifier.现在没有警告,但我不得不删除friend限定符。 Perhaps its a step forward.也许它向前迈出了一步。 Hopes this helps.希望这会有所帮助。

#include <iostream>

template <typename... Args>
class SomeClass {
public:
    class Bar;
    class Foo;
    
    class Foo {
    public:
        bool operator==(const Bar&) const;
    };

    class Bar {
    public:
        bool operator==(const Foo&) const;
    };
    
};

template<typename... Args>
bool SomeClass<Args...>::Foo::operator== (const Bar&) const {
    std::cout << "::Foo\n";
    return true;
}

template<typename... Args>
bool SomeClass<Args...>::Bar::operator== (const Foo& f) const {
    std::cout << "::Bar\n";
    return f == *this;
}

int main() {
    SomeClass<int,float>::Foo foo;
    SomeClass<int,float>::Bar bar;
    std::cout << (foo == bar) << '\n';
    std::cout << (bar == foo) << '\n';
}

The definition of SomeClass<Args...>::Bar::operator== does not redefine the operator, but rather uses the definition of SomeClass<Args...>::Foo::operator== . SomeClass<Args...>::Bar::operator==的定义没有重新定义运算符,而是使用了SomeClass<Args...>::Foo::operator==的定义。

Ok, here are the results of some research I did.好的,这是我所做的一些研究的结果。

In case of the friend in-class definition, according to cppreference , what it does is it generates non-template overloads of operator==.根据cppreference的说法,在朋友类定义的情况下,它会生成 operator== 的非模板重载 Thus, it is perfectly fine to reference them from the second friend declaration, and the code itself is correct.因此,从第二个朋友声明中引用它们是完全可以的,并且代码本身是正确的。 This is also why adding "<>" breaks the befriending: the overloads generated by the first definition are not template functions, while the second definition with the "<>" added now refers to some template overload that isn't definded.这也是为什么添加“<>”会破坏友好:第一个定义生成的重载不是模板函数,而添加“<>”的第二个定义现在指的是一些未定义的模板重载。

Both Clang and MSVC compile that code without any problems, so the warning seems to be a GCC-only thing. Clang 和 MSVC 都可以毫无问题地编译该代码,因此警告似乎只是 GCC 的问题。 I think, it is ok to suppress it.我认为,压制它是可以的。

The alternative would be to make the operator into a template, but unfortunately doing this properly for the parent class template parameters is impossible.另一种方法是将运算符制作成模板,但不幸的是,为父 class 模板参数正确执行此操作是不可能的。 This won't compile (see below why):这不会编译(见下文为什么):

template <typename...>
class SomeClass {
public:
    class Bar;
    class Foo {
        
        template <typename... Ts>
        friend bool operator==(const typename SomeClass<Ts...>::Foo&, 
                               const typename SomeClass<Ts...>::Bar&) { 
            return true;
        }
    };
    class Bar { 
        template <typename... Ts>
        friend bool operator==(const typename SomeClass<Ts...>::Foo&, 
                               const typename SomeClass<Ts...>::Bar&);
    };
};

The real alternative here is to trick GCC by making the operator template with some obsolete arguments, that could be deduced.这里真正的替代方法是通过使用一些过时的 arguments 制作运算符模板来欺骗 GCC,这可以推断出来。 And since default template arguments are forbidden in friend declarations, the only other hack I can think of is to make the operator variadic and use the fact that a trailing parameter pack that is not otherwise deduced, is deduced to an empty parameter pack .并且由于默认模板 arguments 在友元声明中被禁止,我能想到的唯一其他技巧是使运算符可变参数并使用这样一个事实即没有以其他方式推导的尾随参数包被推导为空参数包 This works and emits no warnings in GCC:这在 GCC 中有效并且不会发出警告:

template <typename...>
class SomeClass {
public:
    class Bar;
    class Foo {
        
        template <typename...>
        friend bool operator==(const Foo&, const Bar&) { 
            return true;
        }
    };
    class Bar { 
        template <typename...>
        friend bool operator==(const Foo&, const Bar&);
    };
};

I really see no reason in doing that now, so the best thing to do is to pretend that you never saw this, and that I never wrote this.我现在真的认为没有理由这样做,所以最好的办法就是假装你从未见过这个,我从来没有写过这个。


Now, the second part, the outside definition.现在,第二部分,外部定义。 The problem here is that, as it turns out, there is no way in C++ to deduce template arguments (pack or not) of a parent class from a nested class. The problem here is that, as it turns out, there is no way in C++ to deduce template arguments (pack or not) of a parent class from a nested class.

Ie a thing like this cannot possibly work:即这样的事情不可能起作用:

template <typename T>
void foo(typename Parent<T>::Child) { }

foo(Parent<int>::Child{});

There even was a proposal to resolve this issue, but it was declined.甚至有人提出了解决这个问题的建议,但被拒绝了。

Defining operator== as a template function in my example above has exactly this problem: once the operator is called for SomeClass<int, double>::Foo and SomeClass<int, double>::Bar , the argument deduction is impossible (though the definition itself is correct), thus the compilation fails.在上面的示例中,将operator==定义为模板 function 正是有这个问题:一旦为SomeClass<int, double>::FooSomeClass<int, double>::Bar调用运算符,就不可能进行参数推导(尽管定义本身是正确的),因此编译失败。 This is also the answer to my first question: since the deduction is impossible, a parameter pack is deduced as an empty pack.这也是我第一个问题的答案:由于无法推导,所以将参数包推导为空包。 Not at all what I intended.完全不是我想要的。

The only workaround I came up with is to define the operator for the template non-nested arguments, and then restrict them.我想出的唯一解决方法是为模板非嵌套 arguments 定义运算符,然后限制它们。 I am going to use C++20 concepts for this, although I think a SFINAE solution could be just as fine.我将为此使用 C++20 概念,尽管我认为 SFINAE 解决方案也可以。

This works exactly as intended:这完全按预期工作:

template <typename...>
class SomeClass;

template <typename T>
constexpr bool IsSomeClass = false;

template <typename... Args>
constexpr bool IsSomeClass<SomeClass<Args...>> = true;

template <typename T, typename U>
concept IsProperFooBar = requires {
  typename T::Parent;
  typename U::Parent;
  requires std::same_as<typename T::Parent, typename U::Parent>;
  requires IsSomeClass<typename T::Parent>;
  requires std::same_as<std::decay_t<T>, typename T::Parent::Foo>;
  requires std::same_as<std::decay_t<U>, typename U::Parent::Bar>;
};

template <typename T, typename U> requires IsProperFooBar<T, U>
bool operator==(const T&, const U&);

template <typename... Args>
class SomeClass {
public:
    class Foo {
    public:
      using Parent = SomeClass;

    private:
      template <typename T, typename U> requires IsProperFooBar<T, U>
      friend bool operator==(const T&, const U&);
    };

    class Bar {
    public:
      using Parent = SomeClass;

    private:
      template <typename T, typename U> requires IsProperFooBar<T, U>
      friend bool operator==(const T&, const U&);
    };
};

template <typename T, typename U> requires IsProperFooBar<T, U>
bool operator==(const T&, const U&) {
    return true;
}

The only issue I see with this solution is that there is now a universal template operator== , so the concept will be checked every time you call == for anything, which may slow down compilation and emit unnecessary concept-not-satisfied diagnostic messages for other things.我在这个解决方案中看到的唯一问题是现在有一个通用模板operator== ,因此每次调用==时都会检查概念,这可能会减慢编译速度并发出不必要的概念不满足诊断消息对于其他事情。 On the plus side, there is no need in forward-declaring class Bar anymore, so at least I got that going for me, which is nice:-)从好的方面来说,不再需要前向声明class Bar ,所以至少我明白了,这很好:-)

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

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