93619


SFINAEついて勉強中にわからないことがありました

詳細は長くなってしまったので、始めに単純化して書きます  
主旨としては、どのように`f()`を呼び出せば次のコードがコンパイル可能になるのだろうかということになると思います

[code]

    #include <type_traits>
    #include <iostream>
    
    template <typename T, typename std::enable_if<std::is_integral<T>::value>::type>
    void f()
    {
        std::cout <<  "ok" << std::endl;
    }
    
    int main()
    {
        f<int>();
    }
    
[/code]

## 詳細

llvmの該当コード  
<https://github.com/llvm/llvm-
project/blob/d9a4f936d05c1e8740f5f73da1b149c36d25d02c/libcxx/include/memory#L1731>

該当箇所が変更された時のコミット  
<https://github.com/llvm/llvm-
project/commit/d9a4f936d05c1e8740f5f73da1b149c36d25d02c#diff-0da905341f4329fb01473b012c4374bf81c1abf44dadb93cc2002ef013441401R1716>

変更前

[code]

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY
        static
        typename enable_if
        <
            (__is_default_allocator<allocator_type>::value
                || !__has_construct<allocator_type, _Tp*, _Tp>::value) &&
             is_trivially_move_constructible<_Tp>::value,
            void
        >::type
        __construct_forward_with_exception_guarantees(allocator_type&, _Tp* __begin1, _Tp* __end1, 
        {
            ptrdiff_t _Np = __end1 - __begin1;
            if (_Np > 0)
            {
                _VSTD::memcpy(__begin2, __begin1, _Np * sizeof(_Tp));
                __begin2 += _Np;
            }
        }
    
[/code]

変更後

[code]

    template <class _Alloc, class _Tp, typename enable_if<
        (__is_default_allocator<_Alloc>::value || !__has_construct<_Alloc, _Tp*, _Tp>::value) &&
        is_trivially_move_constructible<_Tp>::value
    >::type>
    _LIBCPP_INLINE_VISIBILITY
    void __construct_forward_with_exception_guarantees(_Alloc&, _Tp* __begin1, _Tp* __end1, _Tp*& __begin2) {
        ptrdiff_t _Np = __end1 - __begin1;
        if (_Np > 0) {
            _VSTD::memcpy(__begin2, __begin1, _Np * sizeof(_Tp));
            __begin2 += _Np;
        }
    }
    
[/code]

変更前は戻り値にenable_ifが使用されていて、trueの場合、  
`void __construct_forward_with_exception_guarantees(略)`  
として実体化されるのに対して、  
変更後は  
`template <class _Alloc, class _Tp, void> void
__construct_forward_with_exception_guarantees(略)`  
のようになりそうで、どうすれば実体化できるのか疑問に思っています。

※追記 現在のllvm最新versionでは別の関数に置き換わっていました  
<https://github.com/llvm/llvm-
project/commit/23cf42e706fbc2a939ce1470da16599b42258aea>

## 余談

発端は、次の(未定義な動作をする)コードを異なるmacで実行した場合に一方はsegv, もう一方は正常終了したことです  
(llvmの具体的なversionは今手元にないので書けません、すいません)

[code]

    #include <vector>
    
    int main()
    {
        std::vector<size_t> v;
        size_t              a[2] = {0, 1};
        v.reserve(1);
        v.insert(v.end() + 1, a, a + 2);
    }
    
[/code]

segvした方のmacでは`__construct_forward_with_exception_guarantees`が変更後の実装で、先述のenable_ifの方は選択されず、  
<https://github.com/llvm/llvm-
project/blob/229db3647491ed2b2706a9b9ce13a97e38be6fa0/libcxx/include/memory#L1461>

[code]

        template <class _Ptr>
            _LIBCPP_INLINE_VISIBILITY
            static
            void
            __construct_forward_with_exception_guarantees(allocator_type& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2)
            {
                static_assert(__is_cpp17_move_insertable<allocator_type>::value,
                  "The specified type does not meet the requirements of Cpp17MoveInsertible");
                for (; __begin1 != __end1; ++__begin1, (void) ++__begin2)
                  construct(__a, _VSTD::__to_address(__begin2),
    #ifdef _LIBCPP_NO_EXCEPTIONS
                            _VSTD::move(*__begin1)
    #else
                            _VSTD::move_if_noexcept(*__begin1)
    #endif
                            );
            }
    
[/code]

が実体化し、  
`__begin1 != __end1`の条件が `end() + 1 != end();`
となって、ループが回り続けることによってsegvしているようでした  
一方変更前の実装ではenable_ifの方が実体化し、  
`ptrdiff_t _Np = __end1 - __begin1;`の結果が負となり、`if (_Np >
0)`で弾かれて何もせずに関数を抜けた結果、segvせずに終了しているようでした  
オーバーロード解決がなぜこのように振舞うのかわからず、デバッガで追ってみたり、llvmのソースを見てみたりしていましたが、自分ではこれ以上のことはわかりませんでした

よろしくお願いします


CC BY-SA 4.0
2023-01-30T04:00:21.367
93614
2023-01-30T09:19:43.947
2023-01-30T06:19:38.963
44647
44647
question
c++ llvm
llvm 14.0.0 libcxx <memory>内に、実体化させる方法が分からない関数テンプレートがあります


LLVM のコードは見るのは大変なので単純化されたものについてしか見れてないのですが、出来ないと考えて良いと思います。

`f` の型引数 `T` が `std::is_integral` の性質に適うとき `type` は `void` に展開されます。
非型テンプレートで使えるのは整数やポインタなどに制限されていて `void` が現れることは出来ませんし、 `void` に対応する値は存在しないので
SFINAE 的には除外されることになります。 (C++20 からは非型テンプレートについて型の制限が大きく緩和されていますがいずれにしても `void`
は駄目です。)

`T` が性質を満たさなければもちろん選択されませんのでどちらにしても呼び出しようのない形式です。

制約を付けるためのものなら

[code]

    template <typename T, typename U = typename std::enable_if<std::is_integral<T>::value>::type>
    
[/code]

とか

[code]

    template <typename T, typename std::enable_if<std::is_integral<T>::value, void*>::type = nullptr>
    
[/code]

とか書くべきで、単純にバグなんじゃないかと思えます。


CC BY-SA 4.0
2023-01-30T09:19:43.947
93619
2023-01-30T09:19:43.947
3364
93614
answer