#include <iostream>
#include <algorithm>
#include <iterator>
#include <cstdlib>
#include <cassert>
#include "Vec.h" // 研究室メンバ共用のライブラリのヘッダファイル
double EPS = 1.0e-10; // Vec.h 内で使用する定数
double DET_EPS = EPS; // 同上
// 比較演算子 (<)
// (数学的な大小を定義しているわけではない.あくまで <algorithm> 用.)
bool operator < (const Lab::VecC& lhs, const Lab::VecC& rhs)
{
assert(lhs.size() == rhs.size());
if (lhs == rhs)
return false;
for (int i = 0; i < lhs.size(); i++)
if (lhs[i] < rhs[i])
return true;
return false;
}
int main(void)
{
const Lab::VecC vectors1[] = {
Lab::VecC(1,2,3), Lab::VecC(4,5,6), Lab::VecC(7,8,9)
};
const Lab::VecC vectors2[] = {
Lab::VecC(2,1,3), Lab::VecC(5,6,4), Lab::VecC(8,9,7)
};
std::set_union(vectors1, vectors1 + 3, vectors2, vectors2 + 3,
std::ostream_iterator<Lab::VecC>(std::cout, " ");
return EXIT_SUCCESS;
}
/usr/lib/gcc/x86_64-pc-linux-gnu/4.1.2/include/g++-v4/bits/stl_algo.h: In function '_OutputIterator std::set_union(_InputIterator1, _InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator) [with _InputIterator1 = const Lab::VecC*, _InputIterator2 = const Lab::VecC*, _OutputIterator = std::ostream_iterator<Lab::VecC, char, std::char_traits<char> >]': main.cc:40: instantiated from here /usr/lib/gcc/x86_64-pc-linux-gnu/4.1.2/include/g++-v4/bits/stl_algo.h:4248: error: no match for 'operator<' in '* __first1 < * __first2' main.cc:40: instantiated from here /usr/lib/gcc/x86_64-pc-linux-gnu/4.1.2/include/g++-v4/bits/stl_algo.h:4253: error: no match for 'operator<' in '* __first2 < * __first1'
何でやねん ! ちゃんと bool operator < (const Lab::VecC&, const Lab::VecC&)
を定義しとるやんけ ! としばらく頭を抱えてた.
試行錯誤する中で,何とはなしに次のように書き換えた.
namespace Lab
{
bool operator < (const VecC& lhs, const VecC& rhs)
{
// 同上
}
}
そしたらコンパイルできた.終わってみれば たったこれだけの事で一体何時間費やしてしまったんだ….疲れた….
Pakuchan
うん.確かにハマるなー.これは.
たぶん俺もハマると思う.
たぶん "Koenig Lookup" もしくは "argument dependent lookup" の影響なんだろうねぇ.
y-iihoshi
> "Koenig Lookup" もしくは "argument dependent lookup" の影響
そのような用語は初めて目にしました.
http://en.wikipedia.org/wiki/Argument_dependent_name_lookup
うーむ,分かったような分からんような.
Pakuchan
Exceptional C++ あたりに書いてあったはず.
その本の説明によれば,
std::cout << "hoge";
がコンパイルでき,std::operator << (std::ostream&, const char*) が呼ばれるのはその機能のおかげだということです.
もしそれがないと,呼び出す関数の名前を名前空間まで含めて書く必要があるので,
std::operator << (std::cout, "hoge");
と書かないとダメなんだぜー,てなコトが書いてありました.
# C++の奥の深さはマリアナ海溝より深い.きっと.
Pakuchan
> もしそれがないと,呼び出す関数の名前を名前空間まで含めて書く必要があるので...
なので,面倒にならないように,関数名を探すときに,「引数の型が属している名前空間」もコンパイラが探してくれる,と.
↓同じKoenig Lookupと関連して,今回とは違うパターンのエラーもあるみたい.
http://ml.tietew.jp/cppll/cppll/article/6932
まあ,名前が曖昧なときのエラーはメッセージに「候補」が書いてあるから,ハマることはない... か.
y-iihoshi
解説どうもw
> Exceptional C++
これはまだ読んだことがないです.
こういう類の本はどんどん買っていきたいと以前から思ってるんですがねぇ.来年度にならないと厳しいかなと.
> 「引数の型が属している名前空間」もコンパイラが探してくれる
ん〜,以下のコードで検証してみたんですが (g++ (GCC) 4.1.2 (Gentoo 4.1.2)),<algorithm> の関数を使っている場合は「引数の型が属している名前空間 “しか” 探してくれていない」ように思えます.しかし何じゃそりゃ.
# indentation にいわゆる全角空白を使っているので注意.
因みに,2 つの operator<() を両方とも有効にした場合は,どちらの sort() を使おうが曖昧性を指摘してくれます.
test.cc: ----------
#include <iostream>
#if defined USE_STL_SORT
# include <algorithm>
#endif
namespace N {
struct T { int i; };
#if !defined USE_GLOBAL_OPERATOR
bool operator<(T t1, T t2) {
std::cout << "in N::operator<(T, T)" << std::endl;
return t1.i < t2.i;
}
#endif
}
#if defined USE_GLOBAL_OPERATOR
bool operator<(N::T t1, N::T t2) {
std::cout << "in ::operator<(N::T, N::T)" << std::endl;
return t1.i < t2.i;
}
#endif
#if !defined USE_STL_SORT
void sort(N::T* begin, N::T* end) {
for (N::T* it1 = begin; it1 != end - 1; it1++)
for (N::T* it2 = it1 + 1; it2 != end; it2++)
if (!(*it1 < *it2)) { N::T temp = *it1; *it1 = *it2; *it2 = temp; }
}
#endif
int main() {
N::T t[] = { {3}, {4}, {2}, {5}, {1} };
#if defined USE_STL_SORT
std::sort(t, t+5);
#else
::sort(t, t+5);
#endif
return 0;
}
terminal: ----------
$ g++ test.cc
$ g++ -DUSE_STL_SORT test.cc
$ g++ -DUSE_GLOBAL_OPERATOR test.cc
$ g++ -DUSE_GLOBAL_OPERATOR -DUSE_STL_SORT test.cc
(STL 特有の怒涛のエラーメッセージ… 超意訳: 適切な operator<() がない)
Pakuchan
おー,USE_GLOBAL_OPERATOR, USE_STL_SORT を定義した場合についての話も,
Exceptional C++ についてちゃんと書かれてました (本の取り上げた例では,std::accumlate と operator + でしたけど).
で,その本での結論は,「コンパイルできるかも知れないし,できないかも知れない」で,理由も書いてあったー.
派生クラスで再定義した名前が基底クラスの名前を隠蔽するのは知っての通りで,それと似たようなことがネストした名前空間でも起こるとのこと(今回の上の例だと,グローバル名前空間と,std名前空間).
つまり,std::operator < がどこかに書かれてしまっていると,コンパイラはグローバルの operator < を見なくなる,というわけ.
# std::sort をコンパイルしている途中に出くわした operator < を探すときに,まず同じ名前空間内(この場合,std)で最初に探索を開始.
# そして std::operator < が見つかったので探索終了.
# グローバルの operator < は out of 眼中.
直接の犯人は <iostream> がインクルードする <iterator> かな?
上のコードを簡略化するとこんな感じ.
----------------------------------------------------------------
// <iostream> をインクルードすると,<iterator> がインクルードされる
// (ある標準ヘッダが裏で何をインクルードするかは処理系依存らしい.で,g++-4.1.2 では <iterator> が入っちゃった,と)
// で,<iterator> をインクルードするとこんなのが入る:
namespace STD {
template<typename T> class reverse_iterator {};
// イテレータ比較用の operator < これがグローバルの operator < を事実上隠蔽する.
template<typename T> bool operator < (const reverse_iterator<T>& x, const reverse_iterator<T>& y) { return true; }
}
// <iterator> インクルード終わり
// <algorithm> によるソート(のつもり):
namespace STD {
template <typename T>
void sort(T* begin, T* end) {
for (T* it1 = begin; it1 != end - 1; it1++)
for (T* it2 = it1 + 1; it2 != end; it2++)
if (!(*it1 < *it2)) { T temp = *it1; *it1 = *it2; *it2 = temp; } // yy行
}
}
// <algorithm> インクルード終わり
namespace N { struct T { int i; }; }
// グローバルな operator <
bool operator < (const N::T& t1, const N::T& t2) { return t1.i < t2.i; }
int main()
{
N::T t[] = { {3}, {4}, {2}, {5}, {1} };
STD::sort(t, t+5); // xx行
return 0;
}
で,こいつをコンパイルすると... こんなエラーが出ます (g++ (GCC) 4.1.2 (Gentoo 4.1.2)).
----------------------------------------------------------------
test.cc: In function 'void STD::sort(T*, T*) [with T = N::T]':
test.cc:xx: instantiated from here
test.cc:yy: error: no match for 'operator<' in '* it1 < * it2'
----------------------------------------------------------------
(xx行, yy行 については上に書いたコードのコメント部を参照)
このエラー,-DUSE_GLOBAL_OPERATOR -DUSE_STL_SORT 定義時のエラーとそっくりな感じ.
ちなみに,STD::operator < の部分をコメントアウトするとコンパイル通ります.
件の本をもっと読むと,
「クラスを名前空間に入れる場合は,すべてのヘルパー関数および演算子関数も同じ名前空間に入れること.これを怠ると,他の場所のコードに多大な影響が及ぶことがある」
だそうです.
というわけで,operator < も namespace N の中に入れてしまうのがベスト.
y-iihoshi
なんか「代わりに調べさせた」感じになってしまって申し訳ないですねw
しかしこれで納得.
俺は普段,普通の関数については名前空間を極力明示するようにしてるんですが,演算子と名前空間との関連についてはこれまで考えたことがなかったですねぇ.今回はいい勉強になりました.
> operator < も namespace N の中に入れてしまうのがベスト
ごもっとも.コード記述の上でも それが一番自然に感じますね.