ユーザー定義クラスの演算子のオーバーロードが機能する方法は、引数依存のルックアップによるものです。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_ops
std::__1::rel_ops
std::rel_ops
std::__1
rel_ops
__1
namespace 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 で動作します。