Rokiのチラ裏

学生による学習のログ

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++オプションだ。このオプションは、FreeBSDOS Xで扱うものだ。
Clangのページを見ると、

Correctness as defined by the C++11 standard

とある。正直、あまり疑いたくはないところだが

Extensive unit tests

ともあるので、もしかするとこれに該当するのかもしれない。
このエントリーを書いているGNU Linux上で、libc++を確認するには、libc++のビルドが必要だ。しかし、幸運な事にも、私はOS Xの入っているMacを所持しているので、そちらで確認する事にした。
今一度、このWandbox上でのみ、tuplearrayを代入できてしまうわけではない事を確認するため、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_likeが使われているコードを辿っていくと、__tuple_convertibleや、__tuple_constructibleなるクラスで使われていた。
流石に様々なマクロやホルダーなどが混在しており全てチェックするのは時間的にも厳しかったので、さらりとしか__tuple_convertibleのコンストラクタの実装を見れていないが、値をgetし、初期化している様子が確認できる。(tupleヘッダ428行目)
...というわけで、libc++のtupleには、規格にはない実装がある事を確認できた。
これは、問題にならないのだろうか。この話題で検索してみても、全くでてこない。
このエントリの冒頭に記載したコードは、確かにセマンティック的には直感的で、理解はできる。がそれとは無関係に、clangについての実装方針について、私は詳しくないので何とも言えないが、規格にないものを、独自拡張としてでなく実装してしまうというのは、いかがなものだろうか。
規格違反かどうかは、明示的に記載されていないので分からない。
何にせよ、標準的でない事は確かであるし、個人的にはこの実装には不安を覚えるところがある。

#追記

@yohhoyさんと@melponさんがツイッターにて、貴重な考察と、文書を見つけて下さりました。ありがとうございます。


という事で、以下の文書に詳細が書かれている。
[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_like trait which controls this behavior. This extension has not been proposed for standardization.

参照