2011-06-03 61 views
37

使用std :: rel_ops將全套關係運算符添加到類中的首選方法是什麼?std :: rel_ops的習慣性使用

This文檔建議一個using namespace std::rel_ops,但是這似乎是有嚴重缺陷的,因爲這將意味着包括以這種方式實現的類的頭也將增加全關係運算符所有其他類與定義的操作<和運營商==,即使那不是我們想要的。這有可能以驚人的方式改變代碼的含義。

作爲一個方面說明 - 我一直在使用Boost.Operators來做到這一點,但我仍然對標準庫感到好奇。

+10

'使用命名空間std :: rel_ops'的另一個問題是操作符不考慮參數相關的查找。這意味着,例如'std :: greater '將無法編譯(而如果在與'my_type'相同的名稱空間或全局名稱空間中定義了合適的'operator>',它將會成功)。 – 2011-06-03 14:17:40

+0

@MikeSeymour我已經添加了一個(不可移植的spec-wise,但在實踐中相當便攜)解決方案,使ADL與rel_ops一起工作。 – bames53 2014-02-13 20:18:56

回答

20

我認爲首選技術不是使用std::rel_ops在 所有。 boost::operatorlink)中使用的技術似乎是通常的 解決方案。

示例:

爲用戶定義的類
#include "boost/operators.hpp" 

class SomeClass : private boost::equivalent<SomeClass>, boost::totally_ordered<SomeClass> 
{ 
public: 
    bool operator<(const SomeClass &rhs) const 
    { 
     return someNumber < rhs.someNumber; 
    } 
private: 
    int someNumber; 
}; 

int main() 
{ 
    SomeClass a, b; 
    a < b; 
    a > b; 
    a <= b; 
    a >= b; 
    a == b; 
    a != b; 
} 
+11

這實際上並沒有回答這個問題。由於其大小和複雜性,許多C++開發項目不包括增強功能。 – 2016-02-16 00:29:10

+7

答案可以擴展到,你知道,_describe_在'boost :: operator'中使用的技術。總的來說,儘管答案是正確的,但是使用'std :: rel_ops'的慣用方法(或者其他任何方法)完全不適用。即使使用操作員來滾動您自己的類模板也會更好。 – 2016-03-13 20:59:38

27

的方式操作符重載是爲了工作是通過參數依賴查找。 ADL允許程序和庫避免使用操作符重載混淆全局名稱空間,但仍然允許操作符的方便使用;也就是說,沒有明確的命名空間限定,這對於中綴運算符語法a + b是不可能的,而是需要正常函數語法your_namespace::operator+ (a, b)

但是,ADL並不是在任何地方搜索任何可能的操作員超載。 ADL僅限於查看「關聯」類和名稱空間。 std::rel_ops的問題在於,如指定的那樣,該名稱空間永遠不可能是標準庫之外定義的任何類的關聯名稱空間,因此ADL無法使用此類用戶定義的類型。

但是,如果你願意作弊,你可以讓std::rel_ops工作。

相關命名空間在C++ 11 3.4.2 [basic.lookup.argdep]/2中定義。出於我們的目的,重要的事實是基類所屬的名稱空間是繼承類的關聯名稱空間,因此ADL將檢查這些名稱空間以獲取適當的功能。

所以,如果執行以下操作:

#include <utility> // rel_ops 
namespace std { namespace rel_ops { struct make_rel_ops_work {}; } } 

是:(莫名其妙)找到進入翻譯單元,然後在支持實現(見下節),那麼你可以定義自己的類的類型,如下所示:

namespace N { 
    // inherit from make_rel_ops_work so that std::rel_ops is an associated namespace for ADL 
    struct S : private std::rel_ops::make_rel_ops_work {}; 

    bool operator== (S const &lhs, S const &rhs) { return true; } 
    bool operator< (S const &lhs, S const &rhs) { return false; } 
} 

然後ADL將努力爲您的類類型,會發現運營商std::rel_ops

#include "S.h" 

#include <functional> // greater 

int main() 
{ 
    N::S a, b; 

    a >= b;      // okay 
    std::greater<N::s>()(a, b); // okay 
} 

過程中添加make_rel_ops_work中自己在技術上使程序具有不確定的行爲,因爲C++不允許用戶程序聲明添加到std。作爲一個例子,這實際上很重要,爲什麼,如果你這樣做,你可能想要驗證你的實現確實能夠正確地使用這個添加,請考慮:

上面我顯示了一個聲明make_rel_ops_work之後是#include <utility>。有人可能會天真地期望,包括這個這裏沒有關係,只要包含頭文件有時在使用運算符重載之前,然後ADL將工作。規範當然沒有這樣的保證,實際情況並非如此。

鐺用的libC++,由於到libC++的使用內聯的命名空間,將(IIUC)考慮的make_rel_ops_work該聲明是在一個不同的命名空間由含有<utility>操作符重載的命名空間除非<utility>「的std::rel_ops的聲明至上。這是因爲在技術上,std::__1::rel_opsstd::rel_ops是不同的名稱空間,即使std::__1是內聯名稱空間。但是,如果clang看到rel_ops的原始名稱空間聲明位於內聯名稱空間__1中,那麼它會將namespace std { namespace rel_ops {聲明視爲擴展std::__1::rel_ops而不是作爲新的名稱空間。

我相信這個命名空間擴展行爲是一個鏗鏘聲擴展,而不是由C++指定的,因此您甚至可能無法在其他實現中依賴此。特別是gcc沒有這樣的行爲,但幸運的是libstdC++不使用內聯命名空間。如果你不想靠這個擴展則鐺/的libC++,你可以這樣寫:

#include <__config> 
_LIBCPP_BEGIN_NAMESPACE_STD 
namespace rel_ops { struct make_rel_ops_work {}; } 
_LIBCPP_END_NAMESPACE_STD 

但很明顯,那麼你就需要爲你使用其他庫的實現。我的簡單聲明make_rel_ops_work適用於clang3.2/libC++,gcc4.7.3/libstdC++和VS2012。

+0

或者你也可以編寫你自己的'std :: rel_ops'版本(可能通過回退'std :: rel_ops'來實現)並避免UB – Justin 2017-12-22 23:27:10

1

這不是最好的,但是您可以使用using namespace std::rel_ops作爲實現您的類型的比較運算符的實現細節。例如:

template <typename T> 
struct MyType 
{ 
    T value; 

    friend bool operator<(MyType const& lhs, MyType const& rhs) 
    { 
     // The type must define `operator<`; std::rel_ops doesn't do that 
     return lhs.value < rhs.value; 
    } 

    friend bool operator<=(MyType const& lhs, MyType const& rhs) 
    { 
     using namespace std::rel_ops; 
     return lhs.value <= rhs.value; 
    } 

    // ... all the other comparison operators 
}; 

通過使用using namespace std::rel_ops;,我們允許ADL查找operator<=,如果它是該類型定義,但是回落到在std::rel_ops定義,否則所述一個。

儘管如此,仍然需要爲每個比較運算符編寫一個函數。