48

std::rel_ops関係演算子の完全なセットをクラスに追加するために使用する好ましい方法は何ですか?

このドキュメントはを示唆していますusing namespace std::rel_opsが、これは深刻な欠陥があるようです。この方法で実装されたクラスのヘッダーを含めると、それが望ましくない場合でも、定義された他のすべてのクラスに完全な関係演算子が追加さoperator<れるoperator==ためです。これは、驚くべき方法でコードの意味を変える可能性があります。

ちなみに、私はこれを行うためにBoost.Operatorsを使用していますが、それでも標準ライブラリに興味があります。

4

4 に答える 4

33

ユーザー定義クラスの演算子のオーバーロードが機能する方法は、引数依存のルックアップによるものです。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++ での clang は、(IIUC)の宣言が、 の宣言が最初に来ない限り、演算子のオーバーロードmake_rel_ops_workを含む名前空間とは別の名前空間にあると見なします。これは、がインラインの名前空間であっても、技術的にはとが別個の名前空間であるためです。しかし、clang は、 の元の名前空間宣言がインライン名前空間にあることを確認すると、宣言を新しい名前空間としてではなく、拡張として扱います。<utility><utility>std::rel_opsstd::__1::rel_opsstd::rel_opsstd::__1rel_ops__1namespace std { namespace rel_ops {std::__1::rel_ops

この名前空間拡張の動作は、C++ で指定されているのではなく、clang 拡張であると考えているため、他の実装ではこれに依存することさえできない場合があります。特に gcc はこのようには動作しませんが、幸いなことに libstdc++ はインライン名前空間を使用しません。この拡張機能に依存したくない場合は、clang/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 で動作します。

于 2014-02-13T20:04:39.253 に答える
25

全く使わない方がいいと思いますstd::rel_opsboost::operatorリンク)で使用されている手法が通常の解決策のようです。

例:

#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;
}
于 2011-06-03T10:26:21.127 に答える