簡體   English   中英

operator== 用於可變參數模板 class 中的類

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

我有以下 class 結構:

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

我需要為SomeClass<Ts...>::FooSomeClass<Ts...>::Bar定義operator==並且我需要讓它成為FooBar的朋友。 我最接近工作的是以下內容:

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&);
    };
};

然后我做:

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

除了 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)

我有點理解為什么會發生這種情況(operator== 確實取決於 SomeClass 的模板參數),但是我該如何擺脫它呢? 正如建議的那樣,將 <> 添加到第二個朋友聲明中只會破壞編譯:不再識別友誼並且編譯器抱怨訪問私有成員。

我試圖根據模板朋友聲明指南修改代碼,但這只會讓事情變得更糟:

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;
}

現在最奇怪的事情發生在我調用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&’

即由於某些奇怪的原因,它嘗試使用空的 arguments 列表調用模板規范並失敗。 更改運算符類型(到 operator+)並沒有幫助(我的想法是,這與 C++20 比較運算符的新規則有關,但不是)。

所以我的問題是:

  1. 這是怎么回事? 我有點困惑,尤其是第二部分:為什么編譯器會嘗試為空參數包調用運算符?
  2. 如何避免第一個解決方案中的警告?
  3. 如何正確定義 class 定義之外的運算符?

我在 class 之外使用operator==的定義得到了這個解決方案。 現在沒有警告,但我不得不刪除friend限定符。 也許它向前邁出了一步。 希望這會有所幫助。

#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';
}

SomeClass<Args...>::Bar::operator==的定義沒有重新定義運算符,而是使用了SomeClass<Args...>::Foo::operator==的定義。

好的,這是我所做的一些研究的結果。

根據cppreference的說法,在朋友類定義的情況下,它會生成 operator== 的非模板重載 因此,從第二個朋友聲明中引用它們是完全可以的,並且代碼本身是正確的。 這也是為什么添加“<>”會破壞友好:第一個定義生成的重載不是模板函數,而添加“<>”的第二個定義現在指的是一些未定義的模板重載。

Clang 和 MSVC 都可以毫無問題地編譯該代碼,因此警告似乎只是 GCC 的問題。 我認為,壓制它是可以的。

另一種方法是將運算符制作成模板,但不幸的是,為父 class 模板參數正確執行此操作是不可能的。 這不會編譯(見下文為什么):

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&);
    };
};

這里真正的替代方法是通過使用一些過時的 arguments 制作運算符模板來欺騙 GCC,這可以推斷出來。 並且由於默認模板 arguments 在友元聲明中被禁止,我能想到的唯一其他技巧是使運算符可變參數並使用這樣一個事實即沒有以其他方式推導的尾隨參數包被推導為空參數包 這在 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&);
    };
};

我現在真的認為沒有理由這樣做,所以最好的辦法就是假裝你從未見過這個,我從來沒有寫過這個。


現在,第二部分,外部定義。 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.

即這樣的事情不可能起作用:

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

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

甚至有人提出了解決這個問題的建議,但被拒絕了。

在上面的示例中,將operator==定義為模板 function 正是有這個問題:一旦為SomeClass<int, double>::FooSomeClass<int, double>::Bar調用運算符,就不可能進行參數推導(盡管定義本身是正確的),因此編譯失敗。 這也是我第一個問題的答案:由於無法推導,所以將參數包推導為空包。 完全不是我想要的。

我想出的唯一解決方法是為模板非嵌套 arguments 定義運算符,然后限制它們。 我將為此使用 C++20 概念,盡管我認為 SFINAE 解決方案也可以。

這完全按預期工作:

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;
}

我在這個解決方案中看到的唯一問題是現在有一個通用模板operator== ,因此每次調用==時都會檢查概念,這可能會減慢編譯速度並發出不必要的概念不滿足診斷消息對於其他事情。 從好的方面來說,不再需要前向聲明class Bar ,所以至少我明白了,這很好:-)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM