clang HEAD 4.0.0 tupleにarrayを初期化、代入できる?
規格書(N4606 § 20.5.2)には載っていないが、Wandbox上でのclang HEAD 4.0.0では、以下のコードが通る。 [Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
std::tuple<int,int>(std::experimental::make_array(42,42));
まずヘッダーファイルを見てみないとなんとも言えないので、実機テストのため、GNU Linuxに、Clangの最新版(執筆時は4.0.0)をビルド、導入し、コンパイルを通してみたが、なんと、コンパイルが通らない。tuple
のヘッダーファイルを確認してみると、std::array
に対する独自的なオーバーロードは見受けられなかった。
一つ疑っていたのは、GNU拡張であったが、Wandboxの方で-std=c++1zオプションを指定しても、結果は変わらなかった。
…となると疑えるところは、libc++オプションだ。このオプションは、FreeBSDやOS Xで扱うものだ。
Clangのページを見ると、
Correctness as defined by the C++11 standard
とある。正直、あまり疑いたくはないところだが
Extensive unit tests
ともあるので、もしかするとこれに該当するのかもしれない。
このエントリーを書いているGNU Linux上で、libc++を確認するには、libc++のビルドが必要だ。しかし、幸運な事にも、私はOS Xの入っているMacを所持しているので、そちらで確認する事にした。
今一度、このWandbox上でのみ、tuple
にarray
を代入できてしまうわけではない事を確認するため、OS X上で上記コードをコンパイルに通してみたが、やはり通る。ほぼ間違いなくlibc++が原因である。
OS X EI Capitan 10.11.2ではlibc++は
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1
にある。
まず、tuple
ヘッダには以下のように宣言されている。
template <class... T> class tuple { public: constexpr tuple(); explicit tuple(const T&...); // constexpr in C++14 template <class... U> explicit tuple(U&&...); // constexpr in C++14 tuple(const tuple&) = default; tuple(tuple&&) = default; template <class... U> tuple(const tuple<U...>&); // constexpr in C++14 template <class... U> tuple(tuple<U...>&&); // constexpr in C++14 template <class U1, class U2> tuple(const pair<U1, U2>&); // iff sizeof...(T) == 2 // constexpr in C++14 template <class U1, class U2> tuple(pair<U1, U2>&&); // iff sizeof...(T) == 2 // constexpr in C++14 // allocator-extended constructors template <class Alloc> tuple(allocator_arg_t, const Alloc& a); template <class Alloc> tuple(allocator_arg_t, const Alloc& a, const T&...); template <class Alloc, class... U> tuple(allocator_arg_t, const Alloc& a, U&&...); template <class Alloc> tuple(allocator_arg_t, const Alloc& a, const tuple&); template <class Alloc> tuple(allocator_arg_t, const Alloc& a, tuple&&); template <class Alloc, class... U> tuple(allocator_arg_t, const Alloc& a, const tuple<U...>&); template <class Alloc, class... U> tuple(allocator_arg_t, const Alloc& a, tuple<U...>&&); template <class Alloc, class U1, class U2> tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); template <class Alloc, class U1, class U2> tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&); tuple& operator=(const tuple&); tuple& operator=(tuple&&) noexcept(AND(is_nothrow_move_assignable<T>::value ...)); template <class... U> tuple& operator=(const tuple<U...>&); template <class... U> tuple& operator=(tuple<U...>&&); template <class U1, class U2> tuple& operator=(const pair<U1, U2>&); // iff sizeof...(T) == 2 template <class U1, class U2> tuple& operator=(pair<U1, U2>&&); //iffsizeof...(T) == 2 void swap(tuple&) noexcept(AND(swap(declval<T&>(), declval<T&>())...)); };
これを見ると、特別array
を受け取るような宣言は見当たらない。この中でarray
を受け取れるとすれば
template <class... U> explicit tuple(U&&...); // constexpr in C++14
辺りだろうか。実装をざっと見てみたが、やはり直接的にarray
を用いているコードは見当たらない。(見逃しているだけかもしれない)
しかし、このtuple
がインクルードしているヘッダ群が気になった。
特に、__tupleというヘッダを見てみると、115行目辺りに、以下のような宣言があった。
// array specializations template <class _Tp, size_t _Size> struct _LIBCPP_TYPE_VIS_ONLY array; template <class _Tp, size_t _Size> struct __tuple_like<array<_Tp, _Size> > : true_type {};
array specializationsと明確に書いてあるのは、やはり意味があるようにしか思えない。
これはとてつもなく気になる宣言文だ。
メタ関数からvalueを判定してtuple
へ入れ込む事が可能かどうか判定しているのだろうか。
_LIBCPP_TYPE_VIS_ONLY
なるマクロは辿っていくと、__configというヘッダ内に定義されていた。内容としては、__WIN32というマクロが定義された場合に定義されるマクロだったので、今回の問題においては無関係なマクロであると言える。
流石に様々なマクロやホルダーなどが混在しており全てチェックするのは時間的にも厳しかったので、さらりとしか__tuple_convertibleのコンストラクタの実装を見れていないが、値をgetし、初期化している様子が確認できる。(tupleヘッダ428行目)
...というわけで、libc++のtupleには、規格にはない実装がある事を確認できた。
これは、問題にならないのだろうか。この話題で検索してみても、
このエントリの冒頭に記載したコードは、確かにセマンティック的には直感的で、理解はできる。がそれとは無関係に、clangについての実装方針について、私は詳しくないので何とも言えないが、規格にないものを、独自拡張としてでなく実装してしまうというのは、いかがなものだろうか。
規格違反かどうかは、明示的に記載されていないので分からない。
何にせよ、標準的でない事は確かであるし、個人的にはこの実装には不安を覚えるところがある。
#追記
@yohhoyさんと@melponさんがツイッターにて、貴重な考察と、文書を見つけて下さりました。ありがとうございます。
うーん std::arrayの要素数が違うとちゃんとrejectする メッセージ見ると https://t.co/ra5jla3yec こいつが誤判定というか過度に許容的に動いてるような気がするんだけど
— yoh (@yohhoy) 2016年8月13日
そうでもないか tuple<> で明示的に array<T,0> 受け入れてるしやっぱり意図的な独自拡張のような気がする
— yoh (@yohhoy) 2016年8月13日
@530506 https://t.co/pHrjggsseu とのことで作者直々に認めてますね
— yoh (@yohhoy) 2016年8月13日
tupleにarray渡せるやつ、前に gitter の nyaocat/cppjp で話題になってたやつだ。k-satodaさんがこれ見つけてた https://t.co/xta0FNOIvZ
— めるぽん(プロのダイエッター) (@melponn) 2016年8月13日
という事で、以下の文書に詳細が書かれている。
[PATCH] [libcxx] Delay evaluation of __make_tuple_types to prevent blowing the max template instantiation depth. Fixes Bug #18345
以下は、文書からの引用である。やはり、この機能は拡張機能であり、標準化のために提案されていない内容との事で、作者が直々に認めている。
This is another extension. This example would already work if A is pair, instead of std::array. libc++ introduces the concept of “tuple-like”, and tuple cleanly interoperates with tuple-like types. The set of tuple-like types are pair and array. One might imagine making complex tuple-like as well. See <__tuple> for the __tuple_liketrait which controls this behavior. This extension has not been proposed for standardization.