Strolling through the code of libcxx's std::span, I noticed that the first two value constructors (number 2 and 3 on cppreference) were not templates.
_LIBCPP_INLINE_VISIBILITY constexpr span(pointer __ptr, index_type __count) : __data{__ptr}
{ (void)__count; _LIBCPP_ASSERT(_Extent == __count, "size mismatch in span's constructor (ptr, len)"); }
_LIBCPP_INLINE_VISIBILITY constexpr span(pointer __f, pointer __l) : __data{__f}
{ (void)__l; _LIBCPP_ASSERT(_Extent == distance(__f, __l), "size mismatch in span's constructor (ptr, ptr)"); }
Instead of the template types It and End as shown on the cppreference page, they use the pointer type directly. So I was wondering whether or not the libcxx code is conforming.
I wanted a point of comparison, so I went ahead and looked at the libstdc++ version, and this one does use templates (and is quite a bit longer as a result).
template<contiguous_iterator _It>
requires __is_compatible_ref<iter_reference_t<_It>>::value
constexpr explicit(extent != dynamic_extent)
span(_It __first, size_type __count)
noexcept
: _M_extent(__count), _M_ptr(std::to_address(__first))
{
if constexpr (_Extent != dynamic_extent)
{
__glibcxx_assert(__count == _Extent);
}
}
template<contiguous_iterator _It, sized_sentinel_for<_It> _End>
requires __is_compatible_ref<iter_reference_t<_It>>::value
&& (!is_convertible_v<_End, size_type>)
constexpr explicit(extent != dynamic_extent)
span(_It __first, _End __last)
noexcept(noexcept(__last - __first))
: _M_extent(static_cast<size_type>(__last - __first)),
_M_ptr(std::to_address(__first))
{
if constexpr (_Extent != dynamic_extent)
{
__glibcxx_assert((__last - __first) == _Extent);
}
}
Now, on the cppreference page, it is mentioned that these two constructors only participate in overload resolution if It satisfies contiguous_iterator and if the conversion from std::iter_reference_t<It> to element_type is at most a qualification conversion. To satisfy that, the libstdc++ code uses the contiguous_iterator concept as template typename (defined here) and the __is_compatible_ref requirement (defined just above).
So, here is my question: is using the pointer type directly, instead of messing with concepts and requirements, actually conforming to the standard? Is the libcxx code correct and the libstdc++ code just overcomplicated?
I would also like to extend the question to the next three constructors (dealing with raw arrays and std::array, number 4, 5, and 6 on cppreference).
The iterator-constructor for std::span you mention was proposed in P1394R4. According to libc++ C++2a Status, this paper has not yet been implemented in libc++.
libc++ implements P0122R7, where there is that pointer-constructor (replaced by the iterator-constructor in P1394R4).
P1872R0 is also related: span should have size_type, not index_type.
is using the pointer type directly, ... actually conforming to the standard?
No, such constructor is not sufficient to provide the template iterator constructor. The quoted / linked libc++ implementation does not conform to the upcoming standard.
libstdc++ template constructor is conforming.
Related
The following code compiles in C++11, C++14, and C++17, but does not compile in C++20. What change to the standard broke this code?
#include <vector>
#include <utility>
template<typename T>
struct bar
{
typename T::reverse_iterator x;
};
struct foo
{
bar<std::vector<std::pair<foo, foo>*>> x;
};
int main()
{
foo f;
}
The error is quite long, but can be summarized as:
template argument must be a complete class
This was always undefined. [res.on.functions]/2.5 says:
In particular, the effects are undefined in the following cases:
[...]
If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
std::pair does not (and cannot) support incomplete types. You were just relying on order of instantiation to kind of get around that. Something changed in the library that slightly changed the evaluation order, leading to the error. But undefined behavior is undefined - it happened to work before and it happens to not work now.
As to why it's specifically C++20 that is causing this to fail. In C++20, iterators changed to have this new iterator_concept idea. In order to instantiate that, reverse_iterator needs to determine what the concept should be. That looks like this:
#if __cplusplus > 201703L && __cpp_lib_concepts
using iterator_concept
= conditional_t<random_access_iterator<_Iterator>,
random_access_iterator_tag,
bidirectional_iterator_tag>;
using iterator_category
= __detail::__clamp_iter_cat<typename __traits_type::iterator_category,
random_access_iterator_tag>;
#endif
Now, in the process of checking random_access_iterator, the root of iterator concept hierarchy is wonderfully named input_or_output_iterator, specified in [iterator.concept.iterator]:
template<class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> can-reference;
} &&
weakly_incrementable<I>;
So, we have to do *i on our iterator type. That's __gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> > , in this case. Now, *i triggers ADL - because of course it does. And ADL requires instantiation of all the associated types - because those associated types might have injected friends that could be candidates!
This, in turn, requires instantiating pair<foo, foo> - because, we have to check. And then that ultimately fails in this specific case because instantiating a type requires instantiating all of the type's special member functions, and the way that libstdc++ implements conditional assignment for std::pair is using Eric Fisellier's trick:
_GLIBCXX20_CONSTEXPR pair&
operator=(typename conditional<
__and_<is_copy_assignable<_T1>,
is_copy_assignable<_T2>>::value,
const pair&, const __nonesuch&>::type __p)
{
first = __p.first;
second = __p.second;
return *this;
}
And is_copy_assignable requires complete types and we don't have one.
But really even if pair used concepts to check in this case, that would still involve instantiating the same type traits, so we'd ultimately end up in the same position.
Moral of the story is, undefined behavior is undefined.
Consider the following:
using vector_type = std::vector<int>;
using const_iterator = typename vector_type::const_iterator;
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
using iterator = typename vector_type::iterator;
using reverse_iterator = typename vector_type::reverse_iterator;
int main()
{
static_assert(!std::is_assignable_v<iterator, const_iterator>); // passes
static_assert(!std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // fails
static_assert(std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // passes
}
I can check that assignment of iterator{} = const_iterator{} is not valid, but not an assignment of reverse_iterator{} = const_reverse_iterator{} with this type trait.
This behavior is consistent across gcc 9.0.0, clang 8.0.0, and MSVC 19.00.23506
This is unfortunate, because the reality is that reverse_iterator{} = const_reverse_iterator{} doesn't actually compile with any of the above-mentioned compilers.
How can I reliably check such an assignment is invalid?
This behavior of the type trait implies that the expression
std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>()
is well formed according to [meta.unary.prop], and this appears consistent with my own attempts at an is_assignable type trait.
This trait passes because the method exists that can be found via overload resolution and it is not deleted.
Actually calling it fails, because the implementation of the method contains code that isn't legal with those two types.
In C++, you cannot test if instantiating a method will result in a compile error, you can only test for the equivalent of overload resolution finding a solution.
The C++ language and standard library originally relied heavily on "well, the method body is only compiled in a template if called, so if the body is invalid the programmer will be told". More modern C++ (both inside and outside the standard library) uses SFINAE and other techniques to make a method "not participate in overload resolution" when its body would not compile.
The constructor of reverse iterator from other reverse iterators is the old style, and hasn't been updated to "not participate in overload resolution" quality.
From n4713, 27.5.1.3.1 [reverse.iter.cons]/3:
template<class U> constexpr reverse_iterator(const reverse_iterator<U>& u);
Effects: Initializes current with u.current.
Notice no mention of "does not participate in overload resolution" or similar words.
The only way to get a trait you want like this would be to
Change (well, fix) the C++ standard
Special case it
I'll leave 1. as an exercise. For 2., you know that reverse iterators are templates over forward iterators.
template<class...Ts>
struct my_trait:std::is_assignable<Ts...> {};
template<class T0, class T1>
struct my_trait<std::reverse_iterator<T0>, std::reverse_iterator<T1>>:
my_trait<T0, T1>
{};
and now my_trait is is_assignable except on reverse iterators, where it instead tests assignability of the contained iterators.
(As extra fun, a reverse reverse iterator will work with this trait).
I once had to do something very similar with std::vector<T>::operator<, which also blindly called T<T and didn't SFINAE disable it if that wasn't legal.
It may also be the case that a C++ standard library implementation can make a constructor which would not compile not participate in overload resolution. This change could break otherwise well formed programs, but only by things as ridiculous as your static assert (which would flip) or things logically equivalent.
It's just a question of constraints. Or lack thereof. Here's a reduced example with a different trait:
struct X {
template <typename T>
X(T v) : i(v) { }
int i;
};
static_assert(is_constructible_v<X, std::string>); // passes
X x("hello"s); // fails
Whenever people talk about being SFINAE-friendly - this is fundamentally what they're referring to. Ensuring that type traits give the correct answer. Here, X claims to be constructible from anything - but really it's not. is_constructible doesn't actually instantiate the entire construction, it just checks the expression validity - it's just a surface-level check. This is the problem that enable_if and later Concepts are intended to solve.
For libstdc++ specifically, we have:
template<typename _Iter>
_GLIBCXX17_CONSTEXPR
reverse_iterator(const reverse_iterator<_Iter>& __x)
: current(__x.base()) { }
There's no constraint on _Iter here, so is_constructible_v<reverse_iterator<T>, reverse_iterator<U>> is true for all pairs T, U, even if it's not actually constructible. The question uses assignable, but in this case, assignment would go through this constructor template, which is why I'm talking about construction.
Note that this is arguably a libstdc++ bug, and probably an oversight. There's even a comment that this should be constrained:
/**
* A %reverse_iterator across other types can be copied if the
* underlying %iterator can be converted to the type of #c current.
*/
The following code works without warning:
std::chrono::duration<unsigned int> d{-17};
I would expect the same warning I get from:
unsigned int x = -17;
Here's the relevant code:
template<typename _Rep2, typename = typename
enable_if<is_convertible<_Rep2, rep>::value
&& (treat_as_floating_point<rep>::value
|| !treat_as_floating_point<_Rep2>::value)>::type>
explicit duration(const _Rep2& __rep)
: __r(static_cast<rep>(__rep)) { }
The static_cast is hiding warnings, and it seems to me that it isn't required for any functionality the standard mandates. Is this just a libc++ problem, or is it required to work this way by the standard?
This is behavior as expected by the standard. The remarks on that constructor are:
This constructor shall not participate in overload resolution unless Rep2 is implicitly
convertible to rep and
(1.1) — treat_as_floating_point_v<rep> is true or
(1.2) — treat_as_floating_point_v<Rep2> is false.
int is implicitly convertible to unsigned int and treat_as_floating_point<int> is false, so we're fine.
The effects are:
Postcondition: count() == static_cast<rep>(r).
libc++ and libstdc++ are both conforming by allowing the code you wrote. It is well-formed. If you think it should be ill-formed, you should submit an issue about it. This isn't a compiler bug. It may be a standard bug.
I'm trying to get a deeper understanding of C++ by reading the C++14 standard along with the source of libc++ and libstdc++. The implementation of various type_traits items varies between the two, particularly is_move_assignable, and I'm trying to figure out which of them is "more correct."
libc++:
template <class _Tp> struct is_move_assignable
: public is_assignable<typename add_lvalue_reference<_Tp>::type,
const typename add_rvalue_reference<_Tp>::type> {};
libstdc++:
template<typename _Tp, bool = __is_referenceable<_Tp>::value>
struct __is_move_assignable_impl;
template<typename _Tp>
struct __is_move_assignable_impl<_Tp, false>
: public false_type { };
template<typename _Tp>
struct __is_move_assignable_impl<_Tp, true>
: public is_assignable<_Tp&, _Tp&&>
{ };
template<typename _Tp>
struct is_move_assignable
: public __is_move_assignable_impl<_Tp>
{ };
The standard states:
For a referenceable type T, the same result as is_assignable<T&, T&&>::value, otherwise false.
The first thing I noted is that libc++ applies const to the second template parameter, which doesn't seem right since the move assignment operator takes a non-const rvalue. libstdc++ also uses __is_referenceable, which follows the wording of the standard, but libc++ doesn't. Is that requirement covered by libc++'s use of add_lvalue_reference and add_rvalue_reference, which both enforce __is_referenceable on their own?
I would really appreciate any insight into why each project chose their solutions!
Thanks! Any idea why the authors might have added const, then?
My best guess is temporary (hopefully) insanity:
https://github.com/llvm-mirror/libcxx/commit/6063ec176d5056683d6ddd310c2e3a8f1c7e1b46#diff-48f5ee43879b5ad38888f0a6ead10113R1245
;-)
I removed the const and ran the current unit tests and nothing failed.
For anything referenceable, the two implementations do the same thing, since the extraneous const in libc++ is meaningless but also harmless.
(Judging from the diff, it certainly looks like temporary insanity to me :) Seems to be a C&P issue from a (wrong) implementation of is_copy_assignable.)
For anything non-referenceable (i.e., cv void or abominable function types), libstdc++ returns false_type.
In libc++, add_{l,r}value_reference returns it unchanged (this depends on an issue resolution that postdates C++14). Sprinkling a const on top does nothing for AFTs and adds a const for the voidy types.
We then go to is_assignable, which SFINAE-tests the well-formedness of declval<T>() = declval<U>(), for either T == U == some AFT or T == some void type and U = some const-qualified void type. In all cases the expression is ill-formed (in a SFINAE-friendly manner), so we get false_type back.
The two are equivalent.
__is_referenceable is a non-standard, internal libstdc++ routine. (That doesn't mean it's bad, just that I wouldn't expect libc++ to use it). Also, the "is referenceable" concept came along much later than is_move_assignable.
The __is_referenceable helps when dealing with "abominable functions"; things like int (*) (double) &&.
Looks like I need to write more tests :-)
Using a slightly modified version of Howard Hinnants's C++11 stack allocator which is documented here and here, with std::basic_string and compiling with gcc which is using libstdc++, the following example (see it live):
const unsigned int N = 200;
arena<N> a;
short_alloc<char, N> ac(a) ;
std::basic_string<char,std::char_traits<char>,short_alloc<char, N>> empty(ac);
gives the following error(amongst others):
error: no matching function for call to 'short_alloc<char, 200ul>::short_alloc()'
if (__n == 0 && __a == _Alloc())
^
However it works without error when compiling with clang and using libc++ (see it live).
The stdlibc++ implementation of std::basic_string expects the allocator to have a default constructor.
Does C++11 require allocators to be default constructible? Which implementation is correct?
No, C++11 does not require an allocator have default constructor, if we look at the draft C++11 standard section 17.6.3.5 [allocator.requirements] it contains Table 28 Allocator requirements which does not contain a requirement for a default constructor and later on in the section a minimal conforming interface is provided:
[ Example: the following is an allocator class template supporting the
minimal interface that satisfies the requirements of Table 28:
template <class Tp>
struct SimpleAllocator {
typedef Tp value_type;
SimpleAllocator(ctor args );
template <class T> SimpleAllocator(const SimpleAllocator<T>& other);
Tp *allocate(std::size_t n);
void deallocate(Tp *p, std::size_t n);
};
—end example ]
which does not contain a default constructor.
There is a libstdc++ bug report: basic_string assumes that allocators are default-constructible which says:
The empty-string optimization of basic_string assumes that allocators
are default constructible. While this used to be the case in C++98, it
is no longer true in C++11, as now allocators are allowed to have
state.
Consider the attached example program. Compiling with
g++ -std=c++11 -c t.cpp
produces an error message, even though it should compile fine. The
problem is the the "_S_construct" calls "_Alloc()", which does not
exist.
Note that the C++11 standard does not require default constructors.
(Section 17.6.3.5, Table 28). In particular, the SimpleAllocator
example from Section 17.6.3.5 would trigger the same bug, too.
and the response was:
This is hardly the only C++11 allocator requirement missing from
std::string, ALL of the new requirements are missing, and unlikely to
be implemented until we switch to a non-COW string implementation.
This is fixed as of gcc 5.0:
Fixed for GCC 5 (when using the new string ABI)
We can confirm this using gcc 5 on wandbox