C++14までの標準規格実装に準ずる厳密なhas_iterator
void_t detection idiomのエントリなどから何度か当ブログで登場している、イテレータを持つtypeであるかを判別するメタ関数has_iteratorであるが、これまでのエントリでは以下のような実装を示してきた。
template<class,class=std::void_t<>> constexpr bool has_iterator_v=std::false_type::value; template<class T> constexpr bool has_iterator_v<T,std::void_t<typename T::iterator>> = std::true_type::value;
しかし、このメタ関数はiteratorクラスを継承したイテレータを持たないクラスに対して、誤った判定結果を返す。下記のコードではstatic_assertがfireして欲しいところだが、残念ながらコンパイルが通ってしまう。
static_assert(has_iterator_v<std::back_insert_iterator<std::vector<int>>>); // passing... ?!
std::back_insert_iterator
はiteratorクラスを継承している。back_insert_iterator
自体はiteratorであるが、iteratorを持っているわけではないので、この判定結果は間違いである。この問題を回避するためには、iteratorというエイリアスが見えているのか、それともiteratorという生のtypeが見えているのか(この場合、継承関係を調査する)を判定すれば良い。(コンパイラごとに分岐するマクロが記述されている理由は当エントリの追記に書かれている。)
表題として、C++14までのとしているのは、C++1zではiteratorクラスがdeprecatedとされる予定*1だからだ。しかし新たにC++1z制定以降に世間で書かれるコードがiteratorクラスを使わないにしても、今まで使われてきた標準ライブラリがどのように実装されるのか(そのままの可能性も大いに考えられる)は不明であるので、依然として対策は必要である。
追記
当エントリの内容は、比較的有名なコンパイラー(ClangやGCC)のバージョンごとで誤った解釈がされる可能性がある。
実際に処理内容として正しいコードは上記にあるものだが、コンパイラのバージョンによっては対応しなければならないかもしれない。clang 3.8.1:https://t.co/Ts3rGKTi0F
— roκi (@530506) 2017年2月7日
GCC 6.1.0:https://t.co/7ldNTL644a
clang 5.0.0 & GCC7.0.1(これが正しい):https://t.co/UxkQRCdBO5
闇を見ている。
原因は今の所不明であるが、ライブラリ実装の問題ではなくコンパイラの解釈自体に問題があるのではないかと私は踏んでいる。最新のXcodeのclang(800.0.42.1)でも間違っていると思われる解釈をする。もうこれはboost config compilerかな...
— roκi (@530506) 2017年2月7日