This is a revision of the previous question. I was told to ask an additional question with more context for the modified code to be reviewed.
The changes:
- changed from
struct
toclass
(for semantic purposes) and made the member variablesprivate
; - added a public member function
get_container
to allow access to the only data member that the user code needs; - added a private type alias
value_type
.
My general aim is to keep things as simple as appropriate. This means I prefer not to use some features (e.g. inheritance, CRTP, etc.) and this may lead to slightly less convenient API which is fine (in my opinion).
Here is the modified code:
template <class Container, std::size_t DefaultBufferCapacity> requires ( std::same_as<typename Container::allocator_type, std::pmr::polymorphic_allocator<typename Container::value_type>> ) class [[ nodiscard ]] buffer_resource_container { public: buffer_resource_container( ) noexcept ( noexcept( Container { &buffer_resource } ) ) = default; auto& get_container( this auto& self ) noexcept { return self.container; } private: using value_type = Container::value_type; alignas ( value_type ) std::array<std::byte, DefaultBufferCapacity * sizeof( value_type )> buffer; std::pmr::monotonic_buffer_resource buffer_resource { std::data( buffer ), std::size( buffer ) }; Container container { &buffer_resource }; }; template <class ValueType, std::size_t DefaultBufferCapacity> using buffer_resource_vector = buffer_resource_container<std::pmr::vector<ValueType>, DefaultBufferCapacity>; template <class ValueType, class Allocator> [[ nodiscard ]] std::errc inline reserve_capacity( std::vector<ValueType, Allocator>& vec, const std::size_t new_capacity ) noexcept { try { vec.reserve( new_capacity ); } catch ( const std::bad_alloc& ) { return std::errc::not_enough_memory; } catch ( const std::length_error& ) { return std::errc::value_too_large; } catch ( const std::exception& ) { return std::errc::resource_unavailable_try_again; } return std::errc { }; }
A good demonstrative case in which I actually use the above code is shown below:
#include <concepts> #include <memory_resource> #include <array> #include <vector> #include <system_error> #include <new> #include <stdexcept> #include <exception> #include <chrono> #include <expected> #include <span> #include <string_view> #include <iterator> #include <algorithm> #include <iostream> #include <cstddef> template <class Container, std::size_t DefaultBufferCapacity> requires ( std::same_as<typename Container::allocator_type, std::pmr::polymorphic_allocator<typename Container::value_type>> ) class [[ nodiscard ]] buffer_resource_container { public: buffer_resource_container( ) noexcept ( noexcept( Container { &buffer_resource } ) ) = default; auto& get_container( this auto& self ) noexcept { return self.container; } private: using value_type = Container::value_type; alignas ( value_type ) std::array<std::byte, DefaultBufferCapacity * sizeof( value_type )> buffer; std::pmr::monotonic_buffer_resource buffer_resource { std::data( buffer ), std::size( buffer ) }; Container container { &buffer_resource }; }; template <class ValueType, std::size_t DefaultBufferCapacity> using buffer_resource_vector = buffer_resource_container<std::pmr::vector<ValueType>, DefaultBufferCapacity>; template <class ValueType, class Allocator> [[ nodiscard ]] std::errc inline reserve_capacity( std::vector<ValueType, Allocator>& vec, const std::size_t new_capacity ) noexcept { try { vec.reserve( new_capacity ); } catch ( const std::bad_alloc& ) { return std::errc::not_enough_memory; } catch ( const std::length_error& ) { return std::errc::value_too_large; } catch ( const std::exception& ) { return std::errc::resource_unavailable_try_again; } return std::errc { }; } namespace { [[ nodiscard ]] bool try_initialize_timezone_database( ) noexcept { bool is_initialized { }; try { [[ maybe_unused ]] const auto& tzdb_list { std::chrono::get_tzdb_list( ) }; is_initialized = true; } catch ( const std::runtime_error& ) { } return is_initialized; } constexpr auto timezone_names_default_count { 1000uz }; [[ nodiscard ]] std::expected<const std::span<const std::string_view>, std::errc> retrieve_all_timezone_names( const bool is_tzdb_initialized ) noexcept { if ( is_tzdb_initialized == false ) return std::unexpected { std::errc::state_not_recoverable }; const auto& tzdb { std::chrono::get_tzdb( ) }; const auto timezone_names_count { tzdb.zones.size( ) + tzdb.links.size( ) }; static buffer_resource_vector<std::string_view, timezone_names_default_count> brv { }; auto& timezone_names_buffer { brv.get_container( ) }; const auto err_code { reserve_capacity( timezone_names_buffer, timezone_names_count ) }; if ( err_code != std::errc { } ) return std::unexpected { err_code }; const auto populate_timezone_names_buffer { [ &tzdb, inserter = std::back_inserter( timezone_names_buffer ) ]( ) noexcept { std::ranges::transform( tzdb.zones, inserter, &std::chrono::time_zone::name ); std::ranges::transform( tzdb.links, inserter, &std::chrono::time_zone_link::name ); } }; populate_timezone_names_buffer( ); return timezone_names_buffer; } } const auto is_tzdb_initialized { try_initialize_timezone_database( ) }; const auto all_timezone_names { retrieve_all_timezone_names( is_tzdb_initialized ) }; int main( ) { if ( is_tzdb_initialized == false ) { std::cout << "The timezone database could not be initialized\n"; return 1; } if ( all_timezone_names.has_value( ) == false ) { std::cout << "An error occurred during buffer allocation\n"; return 1; } const auto timezone_names { *all_timezone_names }; }
Looking at the above demo program, I think that this container wrapper is a good solution for storing a limited number of std::string_view
s without doing a dynamic allocation and still having a fallback to dynamic allocation if the number of timezone names exceeds the default count (i.e. 1000) in the future.
Any suggestions for improvements? Or any potential downsides?
get_tzdb().zones | views::transform([](auto& tz) { return tz.name(); })
whenever you want it. There is no sense in copying the list to another container… let alone a barely functional crippled container. Wrap that in anoexcept
function that returns a failedstd::expected
whenget_tzdb()
fails, and that more or less satisfies all your design requirements.\$\endgroup\$