Within llvm14.0.0 libcxx<memory>, there are function templates that you don't know how to materialize.

Asked 1 years ago, Updated 1 years ago, 320 views

There was something I didn't understand while studying SFINAE

The details have become longer, so I'll write it down in a simplified way.
I think the main idea is how to call f() to make the next code compileable

#include<type_trains>
# 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>();
}

Affected code for llvm
https://github.com/llvm/llvm-project/blob/d9a4f936d05c1e8740f5f73da1b149c36d25d02c/libcxx/include/memory#L1731

Commit when the affected part is changed
https://github.com/llvm/llvm-project/commit/d9a4f936d05c1e8740f5f73da1b149c36d25d02c#diff-0da905341f4329fb01473b012c4374bf81c1abf44dadb93cc2002ef013441401R1716

Before Changes

template<class_Tp>
    _LIBCPP_INLINE_VISIBILITY
    static
    typename enable_if
    <
        (__is_default_allocator<allocator_type>:value
            || !__has_construction<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;
        }
    }

Modified

template<class_Alloc, class_Tp, typename enable_if<
    (_is_default_allocator<_Alloc>::value||!_has_construction<_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;
    }
}

Before the change, enable_if was used for the return value, and if true,
void_construct_forward_with_exception_guarantees (omitted)

in contrast to being materialized as After the change
template<class_Alloc,class_Tp,void>void_construct_forward_with_exception_guarantees (omitted)
It looks like and I wonder how it can be materialized.

*Note: The current version of llvm has been replaced by another function.
https://github.com/llvm/llvm-project/commit/23cf42e706fbc2a939ce1470da16599b42258aea

It started when I ran the following (undefined behavior) code on a different mac, one of which was segv and the other was successful.
(I don't have the specific version of llvm right now, so I can't write it, sorry)

#include<vector>

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

On the segv mac, _construct_forward_with_exception_guarantees is the modified implementation, and the previously mentioned enable_if is not selected and
https://github.com/llvm/llvm-project/blob/229db3647491ed2b2706a9b9ce13a97e38be6fa0/libcxx/include/memory#L1461

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 Cpp 17 MoveInsertible";
            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
                        );
        }

is materialized and
__begin1!=__end1 condition was end()+1!=end(); and it seemed to segv by continuing to rotate the loop.
On the other hand, enable_if was more materialized in the pre-modification implementation and
ptrdiff_t_Np=_end1-__begin1; result was negative, played with if(_Np>0) and left the function without segv.
I didn't understand why overload resolution was acting like this, so I followed it with my debugger and looked at the source of llvm, but I couldn't figure out more

Thank you for your cooperation

c++ llvm

2023-01-31 08:21

1 Answers

LLVM code is hard to see, so I can only see simplified code, but I think you can think of it as not possible.

type expands to void when the T argument of f meets the nature of std::is_integral. Non-typical templates are restricted to integers, pointers, etc., and void cannot appear, and there is no corresponding value for void and will be excluded from SFINAE (C++20 greatly alleviates the type limit for non-typical templates).

If the T does not meet its nature, of course it will not be selected, so either way it will not be called.

For constraints,

template<typenameT,typenameU=typename std::enable_if<std::is_integral<T>::value>::type>

and so on

template<typenameT,typename std::enable_if<std::is_integral<T>::value, void*>::type=nullptr>

It should be written like this, and I think it's just a bug.


2023-01-31 09:23

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.