static_assert( __cplusplus > 2020'99 ); #pragma once #include #include #include #include #include #include namespace Alepha::Hydrogen ::detail:: ConstexprString_m { namespace C { // While a larger size for the maximum might be desirable, this consumes // some constant amount of space in the compiler's symbol table. To be // mindful of that, we want to not get TOO large. // // As compared to the GCC extension which uses a variadic length NTTP // list, this may use less overall memory, in some ways. However, the // benefit of the NTTP variadic list way, for compiler memory usage, is that // the a type using the string as a parameter has a symbol size which // is proportional to the number of chars in the string. // // However, NTTP parameters are pretty large in terms of memory footprint // of the compiler and they do tax the compiler's CPU usage a lot more. // As such, compile time strings of a "reasonable" size are probably the // best we can hope for. const std::size_t maxSize= 128; } inline namespace exports { struct ConstexprString; inline namespace literals { consteval ConstexprString operator ""_cs( const char *s, std::size_t len ); } } struct exports::ConstexprString { private: class BadConstantStringAllocationError : public std::bad_alloc { public: const char * what() const noexcept final { return "Failure to allocate enough space for a a compile-time string."; } }; // Pretend that these are private, despite the fat that they are public. // They have to be public, to work correctly with `template` parameters. public: std::array< char, C::maxSize > storage= {};// Always null terminated std::size_t length= 0; friend consteval ConstexprString literals::operator ""_cs( const char *, std::size_t ); public: constexpr ConstexprString()= default; constexpr ConstexprString( const char *const s, std::size_t len ) { if( len >= C::maxSize ) throw BadConstantStringAllocationError{}; std::copy_n( s, len, storage.begin() ); length= len; } template< std::size_t N > constexpr ConstexprString( const char (&s)[ N ] ) : ConstexprString( s, N ) {} constexpr bool empty() const noexcept { return length == 0; } constexpr std::size_t size() const noexcept { return length; } constexpr auto begin() const noexcept { return storage.begin(); } constexpr auto end() const noexcept { return storage.begin() + length; } constexpr const char *c_str() const noexcept { return &storage[ 0 ]; } constexpr const char *data() const noexcept { return &storage[ 0 ]; } constexpr char *data() noexcept { return &storage[ 0 ]; } friend constexpr ConstexprString operator + ( const ConstexprString &lhs, const ConstexprString &rhs ) { ConstexprString rv; rv.length= lhs.size() + rhs.size(); if( rv.length >= C::maxSize ) throw BadConstantStringAllocationError{}; using std::begin, std::end; const auto next= std::copy( begin( lhs ), end( lhs ), begin( rv.storage ) ); std::copy( begin( rhs ), end( rhs ), next ); return rv; } // The C++20 rules that build more operators upon these operators are fine: friend constexpr bool operator < ( const ConstexprString &lhs, const ConstexprString &rhs ) noexcept { using std::begin, std::end; return std::lexicographical_compare( begin( lhs ), end( lhs ), begin( rhs ), end( rhs ) ); } friend constexpr bool operator == ( const ConstexprString &lhs, const ConstexprString &rhs ) noexcept { using std::begin, std::end; return lhs.size() == rhs.size() and std::equal( begin( lhs ), end( lhs ), begin( rhs ) ); } friend std::ostream & operator << ( std::ostream &os, const ConstexprString &rhs ) { return os << rhs.c_str(); } // Total order is not helpful, here. void operator<=>( ConstexprString )= delete; }; consteval ConstexprString exports::literals::operator ""_cs( const char *const s, const std::size_t len ) { return ConstexprString( s, len ); } } namespace Alepha::Hydrogen::inline exports::inline ConstexprString_m { using namespace detail::ConstexprString_m::exports; } namespace Alepha::Hydrogen::inline exports::inline literals::inline constexpr_string_literals { using namespace ConstexprString_m::literals; }