Skip to content

Commit

Permalink
Containers: STL compatibility for array(view) classes.
Browse files Browse the repository at this point in the history
This was meant to be a task for a lazy Sunday afternoon. It's Thursday.
That says something about the complexity (and shittiness of the C++20
span).
  • Loading branch information
mosra committed Feb 15, 2019
1 parent 01c473a commit 0a13f8d
Show file tree
Hide file tree
Showing 19 changed files with 1,568 additions and 13 deletions.
11 changes: 11 additions & 0 deletions doc/corrade-changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ namespace Corrade {

@subsection corrade-changelog-latest-new New features

@subsubsection corrade-changelog-latest-new-containers Containers library

- Opt-in STL compatibility for @ref Containers::ArrayView,
@ref Containers::StaticArrayView and @ref Containers::StridedArrayView
classes, allowing them to be implicitly converted from @ref std::vector and
@ref std::array
- Opt-in STL compatibility for @ref Containers::Array,
@ref Containers::ArrayView, @ref Containers::StaticArray,
@ref Containers::StaticArrayView and @ref Containers::StridedArrayView
allowing them to be implicitly converted to/from C++2a @cpp std::span @ce

@subsubsection corrade-changelog-latest-new-utility Utility library

- New @ref Utility::Directory::append() and
Expand Down
13 changes: 13 additions & 0 deletions doc/snippets/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERS
FOLDER "Corrade/doc/snippets")
endif()

# Copied verbatim from src/Corrade/Test/CMakeLists.txt, please keep in sync
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8.0") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6.0") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10.0") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.15"))
add_library(snippets-cpp2a STATIC
Containers-stl2a.cpp)
target_link_libraries(snippets-cpp2a PRIVATE CorradeUtility)
set_target_properties(snippets-cpp2a PROPERTIES
CORRADE_CXX_STANDARD 20
FOLDER "Corrade/doc/snippets")
endif()

if(WITH_INTERCONNECT)
add_library(snippets-Interconnect STATIC Interconnect.cpp)
target_link_libraries(snippets-Interconnect PRIVATE CorradeInterconnect)
Expand Down
10 changes: 10 additions & 0 deletions doc/snippets/Containers-stl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,22 @@
DEALINGS IN THE SOFTWARE.
*/

#include "Corrade/Containers/ArrayViewStl.h"
#include "Corrade/Containers/PointerStl.h"
#include "Corrade/Containers/ReferenceStl.h"

using namespace Corrade;

int main() {
{
/* [ArrayView] */
std::vector<int> a;

Containers::ArrayView<int> b = a;
/* [ArrayView] */
static_cast<void>(b);
}

{
/* [Pointer] */
std::unique_ptr<int> a{new int{5}};
Expand Down
70 changes: 70 additions & 0 deletions doc/snippets/Containers-stl2a.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
This file is part of Corrade.
Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
2017, 2018, 2019 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

#include "Corrade/Corrade.h"
#if __has_include(<span>)
#include "Corrade/Containers/ArrayViewStlSpan.h"
#endif

using namespace Corrade;

int main() {
#if __has_include(<span>)
{
/* [ArrayView] */
std::span<int> a;
Containers::ArrayView<int> b = a;

float c[3]{42.0f, 13.37f, -25.0f};
std::span<float, 3> d = Containers::staticArrayView(c);
/* [ArrayView] */
static_cast<void>(b);
static_cast<void>(d);
}

{
int data[3]{};
/* [ArrayView-stupid-span] */
std::span<int> a;
std::span<int, 3> b{data};
Containers::ArrayView<int> c = a; // correct
Containers::ArrayView<int> d = b; // correct
//Containers::StaticArrayView<3, int> e = a; // correctly doesn't compile
Containers::StaticArrayView<3, int> f = b; // correct
//Containers::StaticArrayView<4, int> g = b; // correctly doesn't compile

std::span<int> i = c; // correct
std::span<int, 3> j = c; // incorrectly compiles, UB :(
std::span<int, 3> k = f; // correct
std::span<int, 4> l = f; // incorrectly compiles, UB :(
/* [ArrayView-stupid-span] */
static_cast<void>(d);
static_cast<void>(i);
static_cast<void>(j);
static_cast<void>(k);
static_cast<void>(l);
}
#endif
}
20 changes: 20 additions & 0 deletions src/Corrade/Containers/Array.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ deleter type can be used:
@todo Something like ArrayTuple to create more than one array with single
allocation and proper alignment for each type? How would non-POD types be
constructed in that? Will that be useful in more than one place?
@section Containers-Array-stl STL compatibility
On compilers that support C++2a and @cpp std::span @ce, implicit conversion
of an @ref Array to it is provided in @ref Corrade/Containers/ArrayViewStlSpan.h.
The conversion is provided in a separate header to avoid unconditional
@cpp #include <span> @ce, which significantly affects compile times. The
following table lists allowed conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref Array "Array<T>" | → | @cpp std::span<T> @ce <b></b>
@ref Array "Array<T>" | → | @cpp std::span<const T> @ce <b></b>
@ref Array "const Array<T>" | → | @cpp std::span<const T> @ce <b></b>
There are some dangerous corner cases due to the way @cpp std::span @ce is
designed, see @ref Containers-ArrayView-stl "ArrayView STL compatibility" for
more information.
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
template<class T, class D = void(*)(T*, std::size_t)>
Expand Down Expand Up @@ -258,6 +276,8 @@ class Array {

/**
* @brief Convert to external view representation
*
* @see @ref Containers-Array-stl
*/
template<class U, class = decltype(Implementation::ArrayViewConverter<T, U>::to(std::declval<ArrayView<T>>()))> /*implicit*/ operator U() {
return Implementation::ArrayViewConverter<T, U>::to(*this);
Expand Down
141 changes: 128 additions & 13 deletions src/Corrade/Containers/ArrayView.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ namespace Implementation {
/**
@brief Array view with size information
Immutable wrapper around continuous range of data. Unlike @ref Array this class
doesn't do any memory management. Main use case is passing array along with
size information to functions etc. If @p T is @cpp const @ce type, the class is
implicitly constructible also from const references to @ref Array and
@ref ArrayView of non-const types.
Immutable wrapper around continuous range of data, similar to a dynamic
@cpp std::span @ce from C++2a. Unlike @ref Array this class doesn't do any
memory management. Main use case is passing array along with size information
to functions etc. If @p T is @cpp const @ce type, the class is implicitly
constructible also from const references to @ref Array and @ref ArrayView of
non-const types. There's also a variant with compile-time size information
called @ref StaticArrayView.
Usage example:
Expand All @@ -60,7 +62,99 @@ Usage example:
and the size includes also the zero-terminator (thus in case of
@cpp "hello" @ce the size would be 6, not 5, as one might expect).
@see @ref ArrayView<const void>, @ref StaticArrayView, @ref arrayView(),
@section Containers-ArrayView-stl STL compatibility
Instances of @ref ArrayView and @ref StaticArrayView are convertible from
@ref std::vector and @ref std::array if you include
@ref Corrade/Containers/ArrayViewStl.h. The conversion is provided in a
separate header to avoid unconditional @cpp #include <vector> @ce or
@cpp #include <array> @ce, which significantly affect compile times.
Additionally, the @ref arrayView(T&&) overload also allows for such a
conversion. The following table lists allowed conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref ArrayView "ArrayView<T>" | ← | @ref std::array "std::array<T, size>"
@ref ArrayView "ArrayView<const T>" | ← | @ref std::array "const std::array<T>"
@ref ArrayView<const void> | ← | @ref std::array "const std::array<T>"
@ref ArrayView "ArrayView<T>" | ← | @ref std::vector "std::vector<T, Allocator>"
@ref ArrayView "ArrayView<const T>" | ← | @ref std::vector "const std::vector<T, Allocator>"
@ref ArrayView<const void> | ← | @ref std::vector "const std::vector<T, Allocator>"
@ref StaticArrayView "StaticArrayView<size, T>" | ← | @ref std::array "std::array<T, size>"
@ref StaticArrayView "StaticArrayView<size, const T>" | ← | @ref std::array "const std::array<T, size>"
Example:
@snippet Containers-stl.cpp ArrayView
On compilers that support C++2a and @cpp std::span @ce, implicit conversion
from and also to it is provided in @ref Corrade/Containers/ArrayViewStlSpan.h.
For similar reasons, it's a dedicated header to avoid unconditional
@cpp #include <span> @ce, but this one is even significantly heavier than the
@ref vector "<vector>" etc. includes, so it's separate from the others as well.
The following table lists allowed conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref ArrayView "ArrayView<T>" | ⇆ | @cpp std::span<T> @ce <b></b>
@ref ArrayView "ArrayView<T>" | ← | @cpp std::span<T, size> @ce <b></b>
@ref ArrayView<const void> | ← | @cpp std::span<T> @ce <b></b>
@ref ArrayView<const void> | ← | @cpp std::span<T, size> @ce <b></b>
@ref StaticArrayView "StaticArrayView<size, T>" | ⇆ | @cpp std::span<T, size> @ce <b></b>
@ref StaticArrayView "StaticArrayView<size, T>" | → | @cpp std::span<T> @ce <b></b>
Example:
@snippet Containers-stl2a.cpp ArrayView
<b></b>
@m_class{m-block m-danger}
@par Dangers of fixed-size std::span conversions
The C++2a @cpp std::span @ce class has an implicit all-catching
@cpp span(const Container&) @ce constructor, due to which it's not possible
to implement the conversion to @cpp std::span @ce on Corrade side and
properly check for correct dimensionality *at compile time*. That means
it's possible to accidentally convert an arbitrary @ref ArrayView<int> to
@cpp std::span<int, 37> @ce or @ref StaticArrayView "StaticArrayView<5, int>"
to @cpp std::span<int, 6> @ce and there's nothing Corrade can do to prevent
that. Even worse, the C++ standard
[defines this conversion as Undefined Behavior](https://en.cppreference.com/w/cpp/container/span/span),
instead of, well, throwing an exception or something.
@par
Fortunately, in the other direction at least, conversion of @cpp std::span @ce
to Corrade container classes *can* be (and are) checked at compile time.
@par
@snippet Containers-stl2a.cpp ArrayView-stupid-span
<b></b>
<b></b>
@m_class{m-block m-warning}
@par Conversion from std::initializer_list
The class deliberately *doesn't* provide any conversion from
@ref std::initializer_list, as it would lead to dangerous behavior even in
very simple and seemingly innocent cases --- see the snippet below.
Instead, where it makes sense, functions accepting @ref ArrayView provide
also an overload taking @ref std::initializer_list.
@par
@code{.cpp}
std::initializer_list<int> a{1, 2, 3, 4};
a[2] = 5; // okay
Containers::ArrayView<int> b{1, 2, 3, 4}; // hypothetical, doesn't compile
b[2] = 5; // crash, initializer_list already destructed here
@endcode
Other array classes provide a subset of this STL compatibility as well, see the
documentation of @ref Containers-Array-stl "Array",
@ref Containers-StaticArray-stl "StaticArray" and
@ref Containers-StridedArrayView-stl "StridedArrayView" for more information.
@see @ref ArrayView<const void>, @ref StridedArrayView, @ref arrayView(),
@ref arrayCast(ArrayView<T>)
*/
/* All member functions are const because the view doesn't own the data */
Expand Down Expand Up @@ -138,6 +232,8 @@ template<class T> class ArrayView {

/**
* @brief Construct a view on an external type / from an external representation
*
* @see @ref Containers-ArrayView-stl
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like
Expand All @@ -149,6 +245,8 @@ template<class T> class ArrayView {

/**
* @brief Convert the view to external representation
*
* @see @ref Containers-ArrayView-stl
*/
/* To simplify the implementation, there's no const-adding conversion.
Instead, the implementer is supposed to add an ArrayViewConverter
Expand Down Expand Up @@ -286,11 +384,12 @@ template<class T> class ArrayView {
@brief Constant void array view with size information
Specialization of @ref ArrayView which is convertible from a compile-time
array, @ref Array, @ref ArrayView or @ref StaticArrayView of any type. Size for
particular type is recalculated to size in bytes. This specialization doesn't
provide any accessors besides @ref data(), because it has no use for the
@cpp void @ce type. Instead, use @ref arrayCast(ArrayView<T>) to first cast the
array to a concrete type and then access the particular elements.
array, @ref Array, @ref ArrayView or @ref StaticArrayView of any type and also
any type convertible to them. Size for a particular type is recalculated to
a size in bytes. This specialization doesn't provide any accessors besides
@ref data(), because it has no use for the @cpp void @ce type. Instead, use
@ref arrayCast(ArrayView<T>) to first cast the array to a concrete type and
then access the particular elements.
Usage example:
Expand Down Expand Up @@ -343,6 +442,8 @@ template<> class ArrayView<const void> {

/**
* @brief Construct a view on an external type
*
* @see @ref Containers-ArrayView-stl
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like
Expand Down Expand Up @@ -422,6 +523,8 @@ template<class T> constexpr ArrayView<T> arrayView(ArrayView<T> view) {

/** @relatesalso ArrayView
@brief Make a view on an external type / from an external representation
@see @ref Containers-ArrayView-stl
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like `consume(foo());`,
Expand Down Expand Up @@ -477,11 +580,17 @@ namespace Implementation {
/**
@brief Fixed-size array view
Equivalent to @ref ArrayView, but with compile-time size information.
Convertible from and to @ref ArrayView. Example usage:
Equivalent to @ref ArrayView, but with compile-time size information. Similar
to a fixed-size @cpp std::span @ce from C++2a. Convertible from and to
@ref ArrayView. Example usage:
@snippet Containers.cpp StaticArrayView-usage
@section Containers-StaticArrayView-stl STL compatibility
See @ref Containers-ArrayView-stl "ArrayView STL compatibility" for more
information.
@see @ref staticArrayView(), @ref arrayCast(StaticArrayView<size, T>)
*/
/* Underscore at the end to avoid conflict with member size(). It's ugly, but
Expand Down Expand Up @@ -551,6 +660,8 @@ template<std::size_t size_, class T> class StaticArrayView {

/**
* @brief Construct a view on an external type / from an external representation
*
* @see @ref Containers-StaticArrayView-stl
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like
Expand All @@ -562,6 +673,8 @@ template<std::size_t size_, class T> class StaticArrayView {

/**
* @brief Convert the view to external representation
*
* @see @ref Containers-StaticArrayView-stl
*/
/* To simplify the implementation, there's no ArrayViewConverter
overload. Instead, the implementer is supposed to extend
Expand Down Expand Up @@ -706,6 +819,8 @@ template<std::size_t size, class T> constexpr StaticArrayView<size, T> staticArr

/** @relatesalso StaticArrayView
@brief Make a static view on an external type / from an external representation
@see @ref Containers-StaticArrayView-stl
*/
/* There's no restriction that would disallow creating StaticArrayView from
e.g. std::array<T>&& because that would break uses like `consume(foo());`,
Expand Down
Loading

0 comments on commit 0a13f8d

Please sign in to comment.