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
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.
© 2024 OneMinuteCode. All rights reserved.