Skip to content

Commit add3ab7

Browse files
committed
[libc++] Add workaround to avoid breaking users of <span> when <ranges> are disabled
Back in 3a208c6, we implemented the range-based constructor for <span>. However, in doing so, we removed a previous non-standard constructor that we provided before shipping <ranges>. Unfortunately, that breaks code that was relying on a range-based constructor until we ship all of <ranges>. This patch reintroduces the old non-conforming constructors and tests that were removed in 3a208c6 and uses them whenever <ranges> is not provided (e.g. in LLVM 14). This is only a temporary workaround until we enable <ranges> by default in C++20, which should hopefully happen by LLVM 15. The goal is to cherry-pick this workaround back to the LLVM 14 release branch, since I suspect the constructor removal may otherwise cause breakage out there, like the breakage I saw internally. We could have avoided this situation by waiting for C++20 to be finalized before shipping std::span. For example, we could have guarded it with something like _LIBCPP_HAS_NO_INCOMPLETE_RANGES to prevent users from accidentally starting to depend on it before it is stable. We did not have these mechanisms when std::span was first implemented, though. NOTE: This is a pretty modified version of d4c39f1 since that one didn't apply properly onto the release/14.x branch. (cherry picked from commit d4c39f1) Differential Revision: https://reviews.llvm.org/D121739
1 parent 329fda3 commit add3ab7

File tree

3 files changed

+306
-3
lines changed

3 files changed

+306
-3
lines changed

libcxx/include/span

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,25 @@ struct __is_std_span : false_type {};
170170
template <class _Tp, size_t _Sz>
171171
struct __is_std_span<span<_Tp, _Sz>> : true_type {};
172172

173-
#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
173+
#if defined(_LIBCPP_HAS_NO_CONCEPTS) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
174+
// This is a temporary workaround until we ship <ranges> -- we've unfortunately been
175+
// shipping <span> before its API was finalized, and we used to provide a constructor
176+
// from container types that had the requirements below. To avoid breaking code that
177+
// has started relying on the range-based constructor until we ship all of <ranges>,
178+
// we emulate the constructor requirements like this.
179+
template <class _Range, class _ElementType, class = void>
180+
struct __span_compatible_range : false_type { };
181+
182+
template <class _Range, class _ElementType>
183+
struct __span_compatible_range<_Range, _ElementType, void_t<
184+
enable_if_t<!__is_std_span<remove_cvref_t<_Range>>::value>,
185+
enable_if_t<!__is_std_array<remove_cvref_t<_Range>>::value>,
186+
enable_if_t<!is_array_v<remove_cvref_t<_Range>>>,
187+
decltype(data(declval<_Range>())),
188+
decltype(size(declval<_Range>())),
189+
enable_if_t<is_convertible_v<remove_pointer_t<decltype(data(declval<_Range&>()))>(*)[], _ElementType(*)[]>>
190+
>> : true_type { };
191+
#else
174192
template <class _Range, class _ElementType>
175193
concept __span_compatible_range =
176194
ranges::contiguous_range<_Range> &&
@@ -248,7 +266,22 @@ public:
248266
_LIBCPP_INLINE_VISIBILITY
249267
constexpr span(const array<_OtherElementType, _Extent>& __arr) noexcept : __data{__arr.data()} {}
250268

251-
#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
269+
#if defined(_LIBCPP_HAS_NO_CONCEPTS) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
270+
template <class _Container, class = enable_if_t<
271+
__span_compatible_range<_Container, element_type>::value
272+
>>
273+
_LIBCPP_INLINE_VISIBILITY
274+
constexpr explicit span(_Container& __c) : __data{std::data(__c)} {
275+
_LIBCPP_ASSERT(std::size(__c) == _Extent, "size mismatch in span's constructor (range)");
276+
}
277+
template <class _Container, class = enable_if_t<
278+
__span_compatible_range<const _Container, element_type>::value
279+
>>
280+
_LIBCPP_INLINE_VISIBILITY
281+
constexpr explicit span(const _Container& __c) : __data{std::data(__c)} {
282+
_LIBCPP_ASSERT(std::size(__c) == _Extent, "size mismatch in span's constructor (range)");
283+
}
284+
#else
252285
template <__span_compatible_range<element_type> _Range>
253286
_LIBCPP_INLINE_VISIBILITY
254287
constexpr explicit span(_Range&& __r) : __data{ranges::data(__r)} {
@@ -434,7 +467,18 @@ public:
434467
_LIBCPP_INLINE_VISIBILITY
435468
constexpr span(const array<_OtherElementType, _Sz>& __arr) noexcept : __data{__arr.data()}, __size{_Sz} {}
436469

437-
#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
470+
#if defined(_LIBCPP_HAS_NO_CONCEPTS) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
471+
template <class _Container, class = enable_if_t<
472+
__span_compatible_range<_Container, element_type>::value
473+
>>
474+
_LIBCPP_INLINE_VISIBILITY
475+
constexpr span(_Container& __c) : __data(std::data(__c)), __size{std::size(__c)} {}
476+
template <class _Container, class = enable_if_t<
477+
__span_compatible_range<const _Container, element_type>::value
478+
>>
479+
_LIBCPP_INLINE_VISIBILITY
480+
constexpr span(const _Container& __c) : __data(std::data(__c)), __size{std::size(__c)} {}
481+
#else
438482
template <__span_compatible_range<element_type> _Range>
439483
_LIBCPP_INLINE_VISIBILITY
440484
constexpr span(_Range&& __r) : __data(ranges::data(__r)), __size{ranges::size(__r)} {}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//===---------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===---------------------------------------------------------------------===//
8+
// UNSUPPORTED: c++03, c++11, c++14, c++17
9+
10+
// <span>
11+
12+
// template<class Container>
13+
// constexpr explicit(Extent != dynamic_extent) span(Container&);
14+
// template<class Container>
15+
// constexpr explicit(Extent != dynamic_extent) span(Container const&);
16+
17+
// This test checks for libc++'s non-conforming temporary extension to std::span
18+
// to support construction from containers that look like contiguous ranges.
19+
//
20+
// This extension is only supported when we don't ship <ranges>, and we can
21+
// remove it once we get rid of _LIBCPP_HAS_NO_INCOMPLETE_RANGES.
22+
23+
#include <span>
24+
#include <cassert>
25+
#include <string>
26+
#include <vector>
27+
28+
#include "test_macros.h"
29+
30+
// Look ma - I'm a container!
31+
template <typename T>
32+
struct IsAContainer {
33+
constexpr IsAContainer() : v_{} {}
34+
constexpr size_t size() const {return 1;}
35+
constexpr T *data() {return &v_;}
36+
constexpr const T *data() const {return &v_;}
37+
constexpr T *begin() {return &v_;}
38+
constexpr const T *begin() const {return &v_;}
39+
constexpr T *end() {return &v_ + 1;}
40+
constexpr const T *end() const {return &v_ + 1;}
41+
42+
constexpr T const *getV() const {return &v_;} // for checking
43+
T v_;
44+
};
45+
46+
47+
void checkCV()
48+
{
49+
std::vector<int> v = {1,2,3};
50+
51+
// Types the same
52+
{
53+
std::span< int> s1{v}; // a span< int> pointing at int.
54+
}
55+
56+
// types different
57+
{
58+
std::span<const int> s1{v}; // a span<const int> pointing at int.
59+
std::span< volatile int> s2{v}; // a span< volatile int> pointing at int.
60+
std::span< volatile int> s3{v}; // a span< volatile int> pointing at const int.
61+
std::span<const volatile int> s4{v}; // a span<const volatile int> pointing at int.
62+
}
63+
64+
// Constructing a const view from a temporary
65+
{
66+
std::span<const int> s1{IsAContainer<int>()};
67+
std::span<const int> s3{std::vector<int>()};
68+
(void) s1;
69+
(void) s3;
70+
}
71+
}
72+
73+
74+
template <typename T>
75+
constexpr bool testConstexprSpan()
76+
{
77+
constexpr IsAContainer<const T> val{};
78+
std::span<const T> s1{val};
79+
return s1.data() == val.getV() && s1.size() == 1;
80+
}
81+
82+
template <typename T>
83+
constexpr bool testConstexprSpanStatic()
84+
{
85+
constexpr IsAContainer<const T> val{};
86+
std::span<const T, 1> s1{val};
87+
return s1.data() == val.getV() && s1.size() == 1;
88+
}
89+
90+
template <typename T>
91+
void testRuntimeSpan()
92+
{
93+
IsAContainer<T> val{};
94+
const IsAContainer<T> cVal;
95+
std::span<T> s1{val};
96+
std::span<const T> s2{cVal};
97+
assert(s1.data() == val.getV() && s1.size() == 1);
98+
assert(s2.data() == cVal.getV() && s2.size() == 1);
99+
}
100+
101+
template <typename T>
102+
void testRuntimeSpanStatic()
103+
{
104+
IsAContainer<T> val{};
105+
const IsAContainer<T> cVal;
106+
std::span<T, 1> s1{val};
107+
std::span<const T, 1> s2{cVal};
108+
assert(s1.data() == val.getV() && s1.size() == 1);
109+
assert(s2.data() == cVal.getV() && s2.size() == 1);
110+
}
111+
112+
struct A{};
113+
114+
int main(int, char**)
115+
{
116+
static_assert(testConstexprSpan<int>(), "");
117+
static_assert(testConstexprSpan<long>(), "");
118+
static_assert(testConstexprSpan<double>(), "");
119+
static_assert(testConstexprSpan<A>(), "");
120+
121+
static_assert(testConstexprSpanStatic<int>(), "");
122+
static_assert(testConstexprSpanStatic<long>(), "");
123+
static_assert(testConstexprSpanStatic<double>(), "");
124+
static_assert(testConstexprSpanStatic<A>(), "");
125+
126+
testRuntimeSpan<int>();
127+
testRuntimeSpan<long>();
128+
testRuntimeSpan<double>();
129+
testRuntimeSpan<std::string>();
130+
testRuntimeSpan<A>();
131+
132+
testRuntimeSpanStatic<int>();
133+
testRuntimeSpanStatic<long>();
134+
testRuntimeSpanStatic<double>();
135+
testRuntimeSpanStatic<std::string>();
136+
testRuntimeSpanStatic<A>();
137+
138+
checkCV();
139+
140+
return 0;
141+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//===---------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===---------------------------------------------------------------------===//
8+
// UNSUPPORTED: c++03, c++11, c++14, c++17
9+
10+
// <span>
11+
12+
// template<class Container>
13+
// constexpr explicit(Extent != dynamic_extent) span(Container&);
14+
// template<class Container>
15+
// constexpr explicit(Extent != dynamic_extent) span(Container const&);
16+
17+
// This test checks for libc++'s non-conforming temporary extension to std::span
18+
// to support construction from containers that look like contiguous ranges.
19+
//
20+
// This extension is only supported when we don't ship <ranges>, and we can
21+
// remove it once we get rid of _LIBCPP_HAS_NO_INCOMPLETE_RANGES.
22+
23+
#include <span>
24+
#include <cassert>
25+
#include <deque>
26+
#include <forward_list>
27+
#include <list>
28+
#include <vector>
29+
30+
#include "test_macros.h"
31+
32+
// Look ma - I'm a container!
33+
template <typename T>
34+
struct IsAContainer {
35+
constexpr IsAContainer() : v_{} {}
36+
constexpr size_t size() const {return 1;}
37+
constexpr T *data() {return &v_;}
38+
constexpr const T *data() const {return &v_;}
39+
40+
constexpr const T *getV() const {return &v_;} // for checking
41+
T v_;
42+
};
43+
44+
template <typename T>
45+
struct NotAContainerNoData {
46+
size_t size() const {return 0;}
47+
};
48+
49+
template <typename T>
50+
struct NotAContainerNoSize {
51+
const T *data() const {return nullptr;}
52+
};
53+
54+
template <typename T>
55+
struct NotAContainerPrivate {
56+
private:
57+
size_t size() const {return 0;}
58+
const T *data() const {return nullptr;}
59+
};
60+
61+
template<class T, size_t extent, class container>
62+
std::span<T, extent> createImplicitSpan(container c) {
63+
return {c}; // expected-error {{chosen constructor is explicit in copy-initialization}}
64+
}
65+
66+
int main(int, char**)
67+
{
68+
69+
// Making non-const spans from const sources (a temporary binds to `const &`)
70+
{
71+
std::span<int> s1{IsAContainer<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<int>'}}
72+
std::span<int> s3{std::vector<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<int>'}}
73+
}
74+
75+
// Missing size and/or data
76+
{
77+
std::span<const int> s1{NotAContainerNoData<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
78+
std::span<const int> s3{NotAContainerNoSize<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
79+
std::span<const int> s5{NotAContainerPrivate<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
80+
81+
// Again with the standard containers
82+
std::span<const int> s11{std::deque<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
83+
std::span<const int> s13{std::list<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
84+
std::span<const int> s15{std::forward_list<int>()}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
85+
}
86+
87+
// Not the same type
88+
{
89+
IsAContainer<int> c;
90+
std::span<float> s1{c}; // expected-error {{no matching constructor for initialization of 'std::span<float>'}}
91+
}
92+
93+
// CV wrong
94+
{
95+
IsAContainer<const int> c;
96+
IsAContainer<const volatile int> cv;
97+
IsAContainer< volatile int> v;
98+
99+
std::span< int> s1{c}; // expected-error {{no matching constructor for initialization of 'std::span<int>'}}
100+
std::span< int> s2{v}; // expected-error {{no matching constructor for initialization of 'std::span<int>'}}
101+
std::span< int> s3{cv}; // expected-error {{no matching constructor for initialization of 'std::span<int>'}}
102+
std::span<const int> s4{v}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
103+
std::span<const int> s5{cv}; // expected-error {{no matching constructor for initialization of 'std::span<const int>'}}
104+
std::span< volatile int> s6{c}; // expected-error {{no matching constructor for initialization of 'std::span<volatile int>'}}
105+
std::span< volatile int> s7{cv}; // expected-error {{no matching constructor for initialization of 'std::span<volatile int>'}}
106+
}
107+
108+
// explicit constructor necessary
109+
{
110+
IsAContainer<int> c;
111+
const IsAContainer<int> cc;
112+
113+
createImplicitSpan<int, 1>(c);
114+
createImplicitSpan<int, 1>(cc);
115+
}
116+
117+
return 0;
118+
}

0 commit comments

Comments
 (0)