Skip to content

Commit

Permalink
Containers: C++17 std::string_view compatibility for String{,View}.
Browse files Browse the repository at this point in the history
Had this implemented first but had to wait until Debug was able to
properly print the thing, heh. I'm also not using this new compatibility
in Debug/format() printers because it would mean including String.h as
well -- imagine that causing 33.8k preprocessed lines instead of 33.5
(on libc++), what a nightmare.
  • Loading branch information
mosra committed Feb 19, 2022
1 parent 46f74dc commit cf0bd1f
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/Corrade/Containers/CMakeLists.txt
Expand Up @@ -55,6 +55,7 @@ set(CorradeContainers_HEADERS
StridedArrayViewStl.h
String.h
StringStl.h
StringStlView.h
StringView.h
Triple.h
TripleStl.h)
Expand Down
30 changes: 25 additions & 5 deletions src/Corrade/Containers/String.h
Expand Up @@ -157,17 +157,37 @@ table lists allowed conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref String | ⇆ | @ref std::string
@ref String | ⇆ | @ref std::string (data copy)
Example:
@snippet Containers-stl.cpp String
Because @ref std::string doesn't provide any way to transfer ownership of its
underlying memory, conversion either way always involves an allocation and a
copy. To mitigate the conversion impact, it's recommended to convert
@ref std::string instances to @ref BasicStringView "StringView" instead where
possible.
underlying memory, conversion either way always involves a data copy. To
mitigate the conversion impact, it's recommended to convert @ref std::string
instances to @ref BasicStringView "StringView" instead where possible.
On compilers that support C++17 and @ref std::string_view, *implicit*
conversion from and to it is provided in @ref Corrade/Containers/StringStlView.h.
For similar reasons, it's a dedicated header to avoid unconditional
@cpp #include <string_view> @ce, but this one is even significantly heavier
than the @ref string "<string>" include on certain implementations, so it's
separate from a @ref std::string as well. The following table lists allowed
conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref String | ← | @ref std::string_view (data copy)
@ref String | → | @ref std::string_view
The @ref std::string_view type doesn't have any mutable counterpart, so there's
no differentiation for a @cpp const @ce variant. While creating a
@ref std::string_view from a @ref String creates a non-owning reference without
allocations or copies, converting the other way involves a data copy. To
mitigate the conversion impact, it's recommended to convert
@ref std::string_view instances to @ref BasicStringView "StringView" instead
where possible.
@experimental
*/
Expand Down
7 changes: 5 additions & 2 deletions src/Corrade/Containers/StringStl.h
Expand Up @@ -32,8 +32,11 @@
Including this header allows you to convert a
@ref Corrade::Containers::String / @ref Corrade::Containers::StringView from
and to @ref std::string. See @ref Containers-String-stl "String STL compatibility"
and @ref Containers-BasicStringView-stl "StringView STL compatibility" for more
and to @ref std::string. A separate
@ref Corrade/Containers/StringStlView.h header provides compatibility with
@ref std::string_view from C++17. See
@ref Containers-String-stl "String STL compatibility" and
@ref Containers-BasicStringView-stl "StringView STL compatibility" for more
information.
*/

Expand Down
90 changes: 90 additions & 0 deletions src/Corrade/Containers/StringStlView.h
@@ -0,0 +1,90 @@
#ifndef Corrade_Containers_StringStlView_h
#define Corrade_Containers_StringStlView_h
/*
This file is part of Corrade.
Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
2017, 2018, 2019, 2020, 2021, 2022
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.
*/

/** @file
@brief STL @ref std::string_view compatibility for @ref Corrade::Containers::String and @ref Corrade::Containers::StringView
@m_since_latest
Including this header allows you to convert a
@ref Corrade::Containers::String / @ref Corrade::Containers::StringView from
and to a C++17 @ref std::string_view. A separate
@ref Corrade/Containers/StringStl.h header provides compatibility with
@ref std::string. See
@ref Containers-String-stl "String STL compatibility" and
@ref Containers-BasicStringView-stl "StringView STL compatibility" for more
information.
*/

/* Unlike with Utility/StlForwardString.h, even libstdc++ 11 and libc++ 13 have
a full definition of std::string_view including default parameters in the
<string_view> header, so we can't forward-declare it. Didn't check with
MSVC, but I assume a similar case. */
#include <string_view>

#include "Corrade/Containers/String.h"
#include "Corrade/Containers/StringView.h"

/* Listing these namespaces doesn't add anything to the docs, so don't */
#ifndef DOXYGEN_GENERATING_OUTPUT
namespace Corrade { namespace Containers { namespace Implementation {

/* Since the insanely huge header is included already anyway, it's not that
much extra pain to just have the implementations fully inline as well.
Better than having to compile a dedicated C++17 file just for this, bloat
our binaries and have their symbol tables polluted by tons of STL stuff. */

template<> struct StringConverter<std::string_view> {
static String from(std::string_view other) {
return String{other.data(), other.size()};
}
static std::string_view to(const String& other) {
return std::string_view{other.data(), other.size()};
}
};

template<> struct StringViewConverter<const char, std::string_view> {
static StringView from(std::string_view other) {
return StringView{other.data(), other.size()};
}
static std::string_view to(StringView other) {
return std::string_view{other.data(), other.size()};
}
};

/* There's no mutable variant of std::string_view, so this goes just one
direction */
template<> struct StringViewConverter<char, std::string_view> {
static std::string_view to(MutableStringView other) {
return std::string_view{other.data(), other.size()};
}
};

}}}
#endif

#endif
32 changes: 27 additions & 5 deletions src/Corrade/Containers/StringView.h
Expand Up @@ -181,22 +181,44 @@ affects compile times. The following table lists allowed conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref StringView | ⇆ | @ref std::string
@ref StringView | ⇆ | @ref std::string "const std::string"
@ref MutableStringView | ⇆ | @ref std::string
@ref MutableStringView | → | @ref std::string "const std::string"
@ref StringView | ← | @ref std::string
@ref StringView | ← | @ref std::string "const std::string"
@ref StringView | → | @ref std::string (data copy)
@ref StringView | → | @ref std::string "const std::string" (data copy)
@ref MutableStringView | ← | @ref std::string
@ref MutableStringView | → | @ref std::string (data copy)
@ref MutableStringView | → | @ref std::string "const std::string" (data copy)
Example:
@snippet Containers-stl.cpp StringView
Creating a @ref std::string instance always involves an allocation and a copy,
Creating a @ref std::string instance always involves a data copy,
while going the other way always creates a non-owning reference without
allocations or copies. @ref StringView / @ref MutableStringView created from a
@ref std::string always have @ref StringViewFlag::NullTerminated set, but the
usual conditions regarding views apply --- if the original string is modified,
view pointer, size or the null termination property may not be valid anymore.
On compilers that support C++17 and @ref std::string_view, implicit conversion
from and to it is provided in @ref Corrade/Containers/StringStlView.h. For
similar reasons, it's a dedicated header to avoid unconditional
@cpp #include <string_view> @ce, but this one is even significantly heavier
than the @ref string "<string>" include on certain implementations, so it's
separate from a @ref std::string as well. The following table lists allowed
conversions:
Corrade type | ↭ | STL type
------------------------------- | - | ---------------------
@ref StringView | ⇆ | @ref std::string_view
@ref MutableStringView | → | @ref std::string_view
The @ref std::string_view type doesn't have any mutable counterpart, so there's
no possibility to create a @ref MutableStringView out of it. Because
@ref std::string_view doesn't preserve any information about the string origin,
neither @ref StringViewFlag::NullTerminated nor @ref StringViewFlag::Global is
set in a @ref StringView converted from it.
@experimental
*/
template<class T> class CORRADE_UTILITY_EXPORT BasicStringView {
Expand Down
10 changes: 7 additions & 3 deletions src/Corrade/Containers/Test/CMakeLists.txt
Expand Up @@ -121,9 +121,13 @@ if(NOT CMAKE_CXX_FLAGS MATCHES "-std=")
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.3") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.10"))
corrade_add_test(ContainersOptionalStlTest OptionalStlTest.cpp)
set_target_properties(ContainersOptionalStlTest PROPERTIES
CORRADE_CXX_STANDARD 17
FOLDER "Corrade/Containers/Test")
corrade_add_test(ContainersStringStlViewTest StringStlViewTest.cpp)
set_target_properties(
ContainersOptionalStlTest
ContainersStringStlViewTest
PROPERTIES
CORRADE_CXX_STANDARD 17
FOLDER "Corrade/Containers/Test")
endif()

# Copied verbatim from src/Corrade/Test/CMakeLists.txt, please keep in sync
Expand Down
147 changes: 147 additions & 0 deletions src/Corrade/Containers/Test/StringStlViewTest.cpp
@@ -0,0 +1,147 @@
/*
This file is part of Corrade.
Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
2017, 2018, 2019, 2020, 2021, 2022
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/Containers/StringStlView.h"
#include "Corrade/TestSuite/Tester.h"
#include "Corrade/Utility/DebugStlStringView.h"

namespace Corrade { namespace Containers { namespace Test { namespace {

struct StringStlViewTest: TestSuite::Tester {
explicit StringStlViewTest();

void convertToStlStringView();
void convertToStlStringViewEmpty();
void convertFromStlStringView();
void convertFromStlStringViewEmpty();

void convertViewToStlStringView();
void convertViewToStlStringViewEmpty();
void convertMutableViewToStlStringView();
void convertMutableViewToStlStringViewEmpty();
void convertViewFromStlStringView();
void convertViewFromStlStringViewEmpty();

void convertMutableViewFromStlStringView();
};

StringStlViewTest::StringStlViewTest() {
addTests({&StringStlViewTest::convertToStlStringView,
&StringStlViewTest::convertToStlStringViewEmpty,
&StringStlViewTest::convertFromStlStringView,
&StringStlViewTest::convertFromStlStringViewEmpty,

&StringStlViewTest::convertViewToStlStringView,
&StringStlViewTest::convertViewToStlStringViewEmpty,
&StringStlViewTest::convertMutableViewToStlStringView,
&StringStlViewTest::convertMutableViewToStlStringViewEmpty,
&StringStlViewTest::convertViewFromStlStringView,
&StringStlViewTest::convertViewFromStlStringViewEmpty,

&StringStlViewTest::convertMutableViewFromStlStringView});
}

using namespace Literals;
using namespace std::string_view_literals;

void StringStlViewTest::convertToStlStringView() {
String a = "hello\0!!!"_s;
std::string_view b = a;
CORRADE_COMPARE(b, "hello\0!!!"sv);
CORRADE_COMPARE(b.data(), static_cast<const void*>(a.data()));
}

void StringStlViewTest::convertToStlStringViewEmpty() {
String a;
std::string_view b = a;
CORRADE_COMPARE(b, std::string_view{});
CORRADE_COMPARE(b.data(), static_cast<const void*>(a.data()));
}

void StringStlViewTest::convertFromStlStringView() {
const std::string_view a = "hello\0!!!"sv;
String b = a;
CORRADE_COMPARE(b, "hello\0!!!"_s);
}

void StringStlViewTest::convertFromStlStringViewEmpty() {
const std::string_view a;
String b = a;
CORRADE_COMPARE(b, ""_s);
}

void StringStlViewTest::convertViewToStlStringView() {
StringView a = "hello\0!!!"_s;
std::string_view b = a;
CORRADE_COMPARE(b, "hello\0!!!"sv);
CORRADE_COMPARE(b.data(), static_cast<const void*>(a.data()));
}

void StringStlViewTest::convertViewToStlStringViewEmpty() {
StringView a;
std::string_view b = a;
CORRADE_COMPARE(b, std::string_view{});
CORRADE_COMPARE(b.data(), static_cast<const void*>(nullptr));
}

void StringStlViewTest::convertMutableViewToStlStringView() {
char data[] = "hello\0!!!";
MutableStringView a{data, 9};
std::string_view b = a;
CORRADE_COMPARE(b, "hello\0!!!"sv);
CORRADE_COMPARE(b.data(), static_cast<const void*>(data));
}

void StringStlViewTest::convertMutableViewToStlStringViewEmpty() {
MutableStringView a;
std::string_view b = a;
CORRADE_COMPARE(b, std::string_view{});
CORRADE_COMPARE(b.data(), static_cast<const void*>(nullptr));
}

void StringStlViewTest::convertViewFromStlStringView() {
const std::string_view a = "hello\0!!!"sv;
StringView b = a;
CORRADE_COMPARE(b, "hello\0!!!"_s);
CORRADE_COMPARE(b.data(), static_cast<const void*>(a.data()));
}

void StringStlViewTest::convertViewFromStlStringViewEmpty() {
const std::string_view a = "hello\0!!!"sv;
StringView b = a;
CORRADE_COMPARE(b, "hello\0!!!"_s);
CORRADE_COMPARE(b.data(), static_cast<const void*>(a.data()));
}

void StringStlViewTest::convertMutableViewFromStlStringView() {
/* A string_view should never be convertible to a mutable view */
CORRADE_VERIFY(std::is_convertible<std::string_view, StringView>::value);
CORRADE_VERIFY(!std::is_convertible<std::string_view, MutableStringView>::value);
}

}}}}

CORRADE_TEST_MAIN(Corrade::Containers::Test::StringStlViewTest)
1 change: 1 addition & 0 deletions src/Corrade/Containers/Test/StringViewBenchmark.cpp
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions src/Corrade/Utility/DebugStlStringView.h
Expand Up @@ -49,6 +49,8 @@ namespace Corrade { namespace Utility {
@m_since_latest
*/
inline Debug& operator<<(Debug& debug, std::string_view value) {
/* Not using the StringView STL compatibility to avoid including
StringStlStringView.h which also drags in String.h */
return debug << Containers::StringView{value.data(), value.size()};
}

Expand Down
4 changes: 4 additions & 0 deletions src/Corrade/Utility/FormatStlStringView.h
Expand Up @@ -47,9 +47,13 @@ namespace Corrade { namespace Utility { namespace Implementation {

template<> struct Formatter<std::string_view> {
static std::size_t format(const Containers::MutableStringView& buffer, std::string_view value, int precision, FormatType type) {
/* Not using the StringView STL compatibility to avoid including
StringStlStringView.h which also drags in String.h */
return Formatter<Containers::StringView>::format(buffer, {value.data(), value.size()}, precision, type);
}
static void format(std::FILE* file, std::string_view value, int precision, FormatType type) {
/* Not using the StringView STL compatibility to avoid including
StringStlStringView.h which also drags in String.h */
return Formatter<Containers::StringView>::format(file, {value.data(), value.size()}, precision, type);
}
};
Expand Down

0 comments on commit cf0bd1f

Please sign in to comment.