PR libstdc++/71044 optimize std::filesystem::path construction

This new implementation has a smaller footprint than the previous
implementation, due to replacing std::vector<_Cmpt> with a custom pimpl
type that only needs a single pointer. The _M_type enumeration is also
combined with the pimpl type, by using a tagged pointer, reducing
sizeof(path) further still.

Construction and modification of paths is now done more efficiently, by
splitting the input into a stack-based buffer of string_view objects
instead of a dynamically-allocated vector containing strings. Once the
final size is known only a single allocation is needed to reserve space
for it.  The append and concat operations no longer require constructing
temporary path objects, nor re-parsing the entire native pathname.

This results in algorithmic improvements to path construction, and
working with large paths is much faster.

	PR libstdc++/71044
	* include/bits/fs_path.h (path::path(path&&)): Add noexcept when
	appropriate. Move _M_cmpts instead of reparsing the native pathname.
	(path::operator=(const path&)): Do not define as defaulted.
	(path::operator/=, path::append): Call _M_append.
	(path::concat): Call _M_concat.
	(path::path(string_type, _Type): Change type of first parameter to
	basic_string_view<value_type>.
	(path::_M_append(basic_string_view<value_type>)): New member function.
	(path::_M_concat(basic_string_view<value_type>)): New member function.
	(_S_convert(value_type*, __null_terminated)): Return string view.
	(_S_convert(const value_type*, __null_terminated)): Return string view.
	(_S_convert(value_type*, value_type*))
	(_S_convert(const value_type*, const value_type*)): Add overloads for
	pairs of pointers.
	(_S_convert(_InputIterator, __null_terminated)): Construct string_type
	explicitly, for cases where _S_convert returns a string view.
	(path::_S_is_dir_sep): Replace with non-member is_dir_sep.
	(path::_M_trim, path::_M_add_root_name, path::_M_add_root_dir)
	(path::_M_add_filename): Remove.
	(path::_M_type()): New member function to replace _M_type data member.
	(path::_List): Define new struct type instead of using std::vector.
	(path::_Cmpt::_Cmpt(string_type, _Type, size_t)): Change type of
	first parameter to basic_string_view<value_type>.
	(path::operator+=(const path&)): Do not define inline.
	(path::operator+=(const string_type&)): Call _M_concat.
	(path::operator+=(const value_type*)): Likewise.
	(path::operator+=(value_type)): Likewise.
	(path::operator+=(basic_string_view<value_type>)): Likewise.
	(path::operator/=(const path&)): Do not define inline.
	(path::_M_append(path)): Remove.
	* python/libstdcxx/v6/printers.py (StdPathPrinter): New printer that
	understands the new path::_List type.
	* src/filesystem/std-path.cc (is_dir_sep): New function to replace
	path::_S_is_dir_sep.
	(path::_Parser): New helper class to parse strings as paths.
	(path::_List::_Impl): Define container type for path components.
	(path::_List): Define members.
	(path::operator=(const path&)): Define explicitly, to provide the
	strong exception safety guarantee.
	(path::operator/=(const path&)): Implement manually by processing
	each component of the argument, rather than using _M_split_cmpts
	to parse the entire string again.
	(path::_M_append(string_type)): Likewise.
	(path::operator+=(const path&)): Likewise.
	(path::_M_concat(string_type)): Likewise.
	(path::remove_filename()): Perform trim directly instead of calling
	_M_trim().
	(path::_M_split_cmpts()): Rewrite in terms of _Parser class.
	(path::_M_trim, path::_M_add_root_name, path::_M_add_root_dir)
	(path::_M_add_filename): Remove.
	* testsuite/27_io/filesystem/path/append/source.cc: Test appending a
	string view that aliases the path.
	testsuite/27_io/filesystem/path/concat/strings.cc: Test concatenating
	a string view that aliases the path.

From-SVN: r267106
This commit is contained in:
Jonathan Wakely 2018-12-13 20:33:55 +00:00 committed by Jonathan Wakely
parent 51beaeba8a
commit 4f87bb8d6e
6 changed files with 1467 additions and 250 deletions

View File

@ -1,5 +1,61 @@
2018-12-13 Jonathan Wakely <jwakely@redhat.com>
PR libstdc++/71044
* include/bits/fs_path.h (path::path(path&&)): Add noexcept when
appropriate. Move _M_cmpts instead of reparsing the native pathname.
(path::operator=(const path&)): Do not define as defaulted.
(path::operator/=, path::append): Call _M_append.
(path::concat): Call _M_concat.
(path::path(string_type, _Type): Change type of first parameter to
basic_string_view<value_type>.
(path::_M_append(basic_string_view<value_type>)): New member function.
(path::_M_concat(basic_string_view<value_type>)): New member function.
(_S_convert(value_type*, __null_terminated)): Return string view.
(_S_convert(const value_type*, __null_terminated)): Return string view.
(_S_convert(value_type*, value_type*))
(_S_convert(const value_type*, const value_type*)): Add overloads for
pairs of pointers.
(_S_convert(_InputIterator, __null_terminated)): Construct string_type
explicitly, for cases where _S_convert returns a string view.
(path::_S_is_dir_sep): Replace with non-member is_dir_sep.
(path::_M_trim, path::_M_add_root_name, path::_M_add_root_dir)
(path::_M_add_filename): Remove.
(path::_M_type()): New member function to replace _M_type data member.
(path::_List): Define new struct type instead of using std::vector.
(path::_Cmpt::_Cmpt(string_type, _Type, size_t)): Change type of
first parameter to basic_string_view<value_type>.
(path::operator+=(const path&)): Do not define inline.
(path::operator+=(const string_type&)): Call _M_concat.
(path::operator+=(const value_type*)): Likewise.
(path::operator+=(value_type)): Likewise.
(path::operator+=(basic_string_view<value_type>)): Likewise.
(path::operator/=(const path&)): Do not define inline.
(path::_M_append(path)): Remove.
* python/libstdcxx/v6/printers.py (StdPathPrinter): New printer that
understands the new path::_List type.
* src/filesystem/std-path.cc (is_dir_sep): New function to replace
path::_S_is_dir_sep.
(path::_Parser): New helper class to parse strings as paths.
(path::_List::_Impl): Define container type for path components.
(path::_List): Define members.
(path::operator=(const path&)): Define explicitly, to provide the
strong exception safety guarantee.
(path::operator/=(const path&)): Implement manually by processing
each component of the argument, rather than using _M_split_cmpts
to parse the entire string again.
(path::_M_append(string_type)): Likewise.
(path::operator+=(const path&)): Likewise.
(path::_M_concat(string_type)): Likewise.
(path::remove_filename()): Perform trim directly instead of calling
_M_trim().
(path::_M_split_cmpts()): Rewrite in terms of _Parser class.
(path::_M_trim, path::_M_add_root_name, path::_M_add_root_dir)
(path::_M_add_filename): Remove.
* testsuite/27_io/filesystem/path/append/source.cc: Test appending a
string view that aliases the path.
testsuite/27_io/filesystem/path/concat/strings.cc: Test concatenating
a string view that aliases the path.
* testsuite/27_io/filesystem/path/generation/proximate.cc: Use
preferred directory separators for normalized paths.
* testsuite/27_io/filesystem/path/generation/relative.cc: Likewise.

View File

@ -34,7 +34,6 @@
#include <utility>
#include <type_traits>
#include <vector>
#include <locale>
#include <iosfwd>
#include <iomanip>
@ -45,6 +44,7 @@
#include <bits/locale_conv.h>
#include <ext/concurrence.h>
#include <bits/shared_ptr.h>
#include <bits/unique_ptr.h>
#if defined(_WIN32) && !defined(__CYGWIN__)
# define _GLIBCXX_FILESYSTEM_IS_WINDOWS 1
@ -169,12 +169,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
path(const path& __p) = default;
path(path&& __p) noexcept
: _M_pathname(std::move(__p._M_pathname)), _M_type(__p._M_type)
{
_M_split_cmpts();
__p.clear();
}
path(path&& __p)
#if _GLIBCXX_USE_CXX11_ABI || _GLIBCXX_FULLY_DYNAMIC_STRING == 0
noexcept
#endif
: _M_pathname(std::move(__p._M_pathname)),
_M_cmpts(std::move(__p._M_cmpts))
{ __p.clear(); }
path(string_type&& __source, format = auto_format)
: _M_pathname(std::move(__source))
@ -213,8 +214,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
// assignments
path& operator=(const path& __p) = default;
path& operator=(path&& __p) noexcept;
path& operator=(const path&);
path& operator=(path&&) noexcept;
path& operator=(string_type&& __source);
path& assign(string_type&& __source);
@ -240,17 +241,26 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
template <class _Source>
_Path<_Source>&
operator/=(_Source const& __source)
{ return _M_append(path(__source)); }
{
_M_append(_S_convert(_S_range_begin(__source), _S_range_end(__source)));
return *this;
}
template<typename _Source>
_Path<_Source>&
append(_Source const& __source)
{ return _M_append(path(__source)); }
{
_M_append(_S_convert(_S_range_begin(__source), _S_range_end(__source)));
return *this;
}
template<typename _InputIterator>
_Path<_InputIterator, _InputIterator>&
append(_InputIterator __first, _InputIterator __last)
{ return _M_append(path(__first, __last)); }
{
_M_append(_S_convert(__first, __last));
return *this;
}
// concatenation
@ -271,12 +281,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
template<typename _Source>
_Path<_Source>&
concat(_Source const& __x)
{ return *this += _S_convert(_S_range_begin(__x), _S_range_end(__x)); }
{
_M_concat(_S_convert(_S_range_begin(__x), _S_range_end(__x)));
return *this;
}
template<typename _InputIterator>
_Path<_InputIterator, _InputIterator>&
concat(_InputIterator __first, _InputIterator __last)
{ return *this += _S_convert(__first, __last); }
{
_M_concat(_S_convert(__first, __last));
return *this;
}
// modifiers
@ -402,30 +418,41 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
private:
enum class _Type : unsigned char {
_Multi, _Root_name, _Root_dir, _Filename
_Multi = 0, _Root_name, _Root_dir, _Filename
};
path(string_type __str, _Type __type) : _M_pathname(__str), _M_type(__type)
path(basic_string_view<value_type> __str, _Type __type)
: _M_pathname(__str)
{
__glibcxx_assert(_M_type != _Type::_Multi);
__glibcxx_assert(__type != _Type::_Multi);
_M_cmpts.type(__type);
}
enum class _Split { _Stem, _Extension };
path& _M_append(path __p);
void _M_append(basic_string_view<value_type>);
void _M_concat(basic_string_view<value_type>);
pair<const string_type*, size_t> _M_find_extension() const;
template<typename _CharT>
struct _Cvt;
static string_type
static basic_string_view<value_type>
_S_convert(value_type* __src, __null_terminated)
{ return string_type(__src); }
{ return __src; }
static string_type
static basic_string_view<value_type>
_S_convert(const value_type* __src, __null_terminated)
{ return string_type(__src); }
{ return __src; }
static basic_string_view<value_type>
_S_convert(value_type* __first, value_type* __last)
{ return {__first, __last - __first}; }
static basic_string_view<value_type>
_S_convert(const value_type* __first, const value_type* __last)
{ return {__first, __last - __first}; }
template<typename _Iter>
static string_type
@ -440,8 +467,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
static string_type
_S_convert(_InputIterator __src, __null_terminated)
{
// Read from iterator into basic_string until a null value is seen:
auto __s = _S_string_from_iter(__src);
return _S_convert(__s.c_str(), __s.c_str() + __s.size());
// Convert (if needed) from iterator's value type to path::value_type:
return string_type(_S_convert(__s.data(), __s.data() + __s.size()));
}
static string_type
@ -469,27 +498,65 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
static basic_string<_CharT, _Traits, _Allocator>
_S_str_convert(const string_type&, const _Allocator& __a);
bool _S_is_dir_sep(value_type __ch)
{
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
return __ch == L'/' || __ch == preferred_separator;
#else
return __ch == '/';
#endif
}
void _M_split_cmpts();
void _M_trim();
void _M_add_root_name(size_t __n);
void _M_add_root_dir(size_t __pos);
void _M_add_filename(size_t __pos, size_t __n);
_Type _M_type() const noexcept { return _M_cmpts.type(); }
string_type _M_pathname;
struct _Cmpt;
using _List = _GLIBCXX_STD_C::vector<_Cmpt>;
_List _M_cmpts; // empty unless _M_type == _Type::_Multi
_Type _M_type = _Type::_Filename;
struct _List
{
using value_type = _Cmpt;
using iterator = value_type*;
using const_iterator = const value_type*;
_List();
_List(const _List&);
_List(_List&&) = default;
_List& operator=(const _List&);
_List& operator=(_List&&) = default;
~_List() = default;
_Type type() const noexcept
{ return _Type{reinterpret_cast<uintptr_t>(_M_impl.get()) & 0x3}; }
void type(_Type) noexcept;
int size() const noexcept; // zero unless type() == _Type::_Multi
bool empty() const noexcept; // true unless type() == _Type::_Multi
void clear();
void swap(_List& __l) noexcept { _M_impl.swap(__l._M_impl); }
int capacity() const noexcept;
void reserve(int, bool); ///< @pre type() == _Type::_Multi
// All the member functions below here have a precondition !empty()
// (and they should only be called from within the library).
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
value_type& front() noexcept;
value_type& back() noexcept;
const value_type& front() const noexcept;
const value_type& back() const noexcept;
void erase(const_iterator);
void erase(const_iterator, const_iterator);
struct _Impl;
struct _Impl_deleter
{
void operator()(_Impl*) const noexcept;
};
unique_ptr<_Impl, _Impl_deleter> _M_impl;
};
_List _M_cmpts;
struct _Parser;
};
inline void swap(path& __lhs, path& __rhs) noexcept { __lhs.swap(__rhs); }
@ -605,8 +672,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
struct path::_Cmpt : path
{
_Cmpt(string_type __s, _Type __t, size_t __pos)
: path(std::move(__s), __t), _M_pos(__pos) { }
_Cmpt(basic_string_view<value_type> __s, _Type __t, size_t __pos)
: path(__s, __t), _M_pos(__pos) { }
_Cmpt() : _M_pos(-1) { }
@ -733,7 +800,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
private:
friend class path;
bool _M_is_multi() const { return _M_path->_M_type == _Type::_Multi; }
bool _M_is_multi() const { return _M_path->_M_type() == _Type::_Multi; }
friend difference_type
__path_iter_distance(const iterator& __first, const iterator& __last)
@ -785,7 +852,6 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
{
_M_pathname = std::move(__p._M_pathname);
_M_cmpts = std::move(__p._M_cmpts);
_M_type = __p._M_type;
__p.clear();
return *this;
}
@ -798,41 +864,31 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
path::assign(string_type&& __source)
{ return *this = path(std::move(__source)); }
inline path&
path::operator+=(const path& __p)
{
return operator+=(__p.native());
}
inline path&
path::operator+=(const string_type& __x)
{
_M_pathname += __x;
_M_split_cmpts();
_M_concat(__x);
return *this;
}
inline path&
path::operator+=(const value_type* __x)
{
_M_pathname += __x;
_M_split_cmpts();
_M_concat(__x);
return *this;
}
inline path&
path::operator+=(value_type __x)
{
_M_pathname += __x;
_M_split_cmpts();
_M_concat(basic_string_view<value_type>(&__x, 1));
return *this;
}
inline path&
path::operator+=(basic_string_view<value_type> __x)
{
_M_pathname.append(__x.data(), __x.size());
_M_split_cmpts();
_M_concat(__x);
return *this;
}
@ -858,7 +914,6 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
{
_M_pathname.swap(__rhs._M_pathname);
_M_cmpts.swap(__rhs._M_cmpts);
std::swap(_M_type, __rhs._M_type);
}
template<typename _CharT, typename _Traits, typename _Allocator>
@ -968,7 +1023,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
#endif
string_type __str(__a);
if (_M_type == _Type::_Root_dir)
if (_M_type() == _Type::_Root_dir)
__str.assign(1, __slash);
else
{
@ -979,7 +1034,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
if (__add_slash)
__str += __slash;
__str += __elem._M_pathname;
__add_slash = __elem._M_type == _Type::_Filename;
__add_slash = __elem._M_type() == _Type::_Filename;
}
}
@ -1026,14 +1081,14 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
{
if (empty())
return {};
else if (_M_type == _Type::_Filename)
else if (_M_type() == _Type::_Filename)
return *this;
else if (_M_type == _Type::_Multi)
else if (_M_type() == _Type::_Multi)
{
if (_M_pathname.back() == preferred_separator)
return {};
auto& __last = *--end();
if (__last._M_type == _Type::_Filename)
if (__last._M_type() == _Type::_Filename)
return __last;
}
return {};
@ -1084,7 +1139,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
inline path::iterator
path::begin() const
{
if (_M_type == _Type::_Multi)
if (_M_type() == _Type::_Multi)
return iterator(this, _M_cmpts.begin());
return iterator(this, empty());
}
@ -1092,48 +1147,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
inline path::iterator
path::end() const
{
if (_M_type == _Type::_Multi)
if (_M_type() == _Type::_Multi)
return iterator(this, _M_cmpts.end());
return iterator(this, true);
}
#ifndef _GLIBCXX_FILESYSTEM_IS_WINDOWS
inline path& path::operator/=(const path& __p)
{
// Much simpler than the specification in the standard,
// as any path with root-name or root-dir is absolute.
if (__p.is_absolute())
operator=(__p);
else
{
if (has_filename() || (_M_type == _Type::_Root_name))
_M_pathname += preferred_separator;
_M_pathname += __p.native();
_M_split_cmpts();
}
return *this;
}
#endif
inline path&
path::_M_append(path __p)
{
if (__p.is_absolute())
operator=(std::move(__p));
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
else if (__p.has_root_name() && __p.root_name() != root_name())
operator=(std::move(__p));
#endif
else
operator/=(const_cast<const path&>(__p));
return *this;
}
inline path::iterator&
path::iterator::operator++()
{
__glibcxx_assert(_M_path != nullptr);
if (_M_path->_M_type == _Type::_Multi)
if (_M_path->_M_type() == _Type::_Multi)
{
__glibcxx_assert(_M_cur != _M_path->_M_cmpts.end());
++_M_cur;
@ -1150,7 +1173,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
path::iterator::operator--()
{
__glibcxx_assert(_M_path != nullptr);
if (_M_path->_M_type == _Type::_Multi)
if (_M_path->_M_type() == _Type::_Multi)
{
__glibcxx_assert(_M_cur != _M_path->_M_cmpts.begin());
--_M_cur;
@ -1167,7 +1190,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
path::iterator::operator*() const
{
__glibcxx_assert(_M_path != nullptr);
if (_M_path->_M_type == _Type::_Multi)
if (_M_path->_M_type() == _Type::_Multi)
{
__glibcxx_assert(_M_cur != _M_path->_M_cmpts.end());
return *_M_cur;
@ -1182,7 +1205,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
return false;
if (_M_path == nullptr)
return true;
if (_M_path->_M_type == path::_Type::_Multi)
if (_M_path->_M_type() == path::_Type::_Multi)
return _M_cur == __rhs._M_cur;
return _M_at_end == __rhs._M_at_end;
}

View File

@ -1244,6 +1244,77 @@ class StdExpPathPrinter:
def children(self):
return self._iterator(self.val['_M_cmpts'])
class StdPathPrinter:
"Print a std::filesystem::path"
def __init__ (self, typename, val):
self.val = val
self.typename = typename
impl = self.val['_M_cmpts']['_M_impl']['_M_t']['_M_t']['_M_head_impl']
self.type = impl.cast(gdb.lookup_type('uintptr_t')) & 3
if self.type == 0:
self.impl = impl
else:
self.impl = None
def _path_type(self):
t = str(self.type.cast(gdb.lookup_type(self.typename + '::_Type')))
if t[-9:] == '_Root_dir':
return "root-directory"
if t[-10:] == '_Root_name':
return "root-name"
return None
def to_string (self):
path = "%s" % self.val ['_M_pathname']
if self.type != 0:
t = self._path_type()
if t:
path = '%s [%s]' % (path, t)
return "filesystem::path %s" % path
class _iterator(Iterator):
def __init__(self, impl, pathtype):
if impl:
# We can't access _Impl::_M_size because _Impl is incomplete
# so cast to int* to access the _M_size member at offset zero,
int_type = gdb.lookup_type('int')
cmpt_type = gdb.lookup_type(pathtype+'::_Cmpt')
char_type = gdb.lookup_type('char')
impl = impl.cast(int_type.pointer())
size = impl.dereference()
#self.capacity = (impl + 1).dereference()
if hasattr(gdb.Type, 'alignof'):
sizeof_Impl = max(2 * int_type.sizeof, cmpt_type.alignof)
else:
sizeof_Impl = 2 * int_type.sizeof
begin = impl.cast(char_type.pointer()) + sizeof_Impl
self.item = begin.cast(cmpt_type.pointer())
self.finish = self.item + size
self.count = 0
else:
self.item = None
self.finish = None
def __iter__(self):
return self
def __next__(self):
if self.item == self.finish:
raise StopIteration
item = self.item.dereference()
count = self.count
self.count = self.count + 1
self.item = self.item + 1
path = item['_M_pathname']
t = StdPathPrinter(item.type.name, item)._path_type()
if not t:
t = count
return ('[%s]' % t, path)
def children(self):
return self._iterator(self.impl, self.typename)
class StdPairPrinter:
"Print a std::pair object, with 'first' and 'second' as children"
@ -1759,9 +1830,9 @@ def build_libstdcxx_dictionary ():
libstdcxx_printer.add_version('std::experimental::filesystem::v1::__cxx11::',
'path', StdExpPathPrinter)
libstdcxx_printer.add_version('std::filesystem::',
'path', StdExpPathPrinter)
'path', StdPathPrinter)
libstdcxx_printer.add_version('std::filesystem::__cxx11::',
'path', StdExpPathPrinter)
'path', StdPathPrinter)
# C++17 components
libstdcxx_printer.add_version('std::',

File diff suppressed because it is too large Load Diff

View File

@ -112,6 +112,33 @@ test04()
#endif
}
void
test05()
{
std::basic_string_view<path::value_type> s;
path p = "0/1/2/3/4/5/6";
// The string_view aliases the path's internal string:
s = p.native();
// Append that string_view, which must work correctly even though the
// internal string will be reallocated during the operation:
p /= s;
VERIFY( p.string() == "0/1/2/3/4/5/6/0/1/2/3/4/5/6" );
// Same again with a trailing slash:
path p2 = "0/1/2/3/4/5/";
s = p2.native();
p2 /= s;
VERIFY( p2.string() == "0/1/2/3/4/5/0/1/2/3/4/5/" );
// And aliasing one of the components of the path:
path p3 = "0/123456789/a";
path::iterator second = std::next(p3.begin());
s = second->native();
p3 /= s;
VERIFY( p3.string() == "0/123456789/a/123456789" );
}
int
main()
{
@ -119,4 +146,5 @@ main()
test02();
test03();
test04();
test05();
}

View File

@ -57,8 +57,36 @@ test01()
VERIFY( p.filename().string() == file );
}
void
test02()
{
std::basic_string_view<path::value_type> s;
path p = "0/1/2/3/4/5/6";
// The string_view aliases the path's internal string:
s = p.native();
// Append that string_view, which must work correctly even though the
// internal string will be reallocated during the operation:
p += s;
VERIFY( p.string() == "0/1/2/3/4/5/60/1/2/3/4/5/6" );
// Same again with a trailing slash:
path p2 = "0/1/2/3/4/5/";
s = p2.native();
p2 += s;
VERIFY( p2.string() == "0/1/2/3/4/5/0/1/2/3/4/5/" );
// And aliasing one of the components of the path:
path p3 = "0/123456789";
path::iterator second = std::next(p3.begin());
s = second->native();
p3 += s;
VERIFY( p3.string() == "0/123456789123456789" );
}
int
main()
{
test01();
test02();
}