Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 194 additions & 100 deletions include/boost/static_string/static_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <limits>
#include <iosfwd>
#include <type_traits>
#include <utility>

namespace boost {
namespace static_strings {
Expand Down Expand Up @@ -549,11 +550,22 @@ inline
static_string<N>
to_static_string_int_impl(Integer value) noexcept
{
char buffer[N];
const auto digits_end = std::end(buffer);
const auto digits_begin = integer_to_string<std::char_traits<char>, Integer>(
digits_end, value, std::is_signed<Integer>{});
return static_string<N>(digits_begin, std::distance(digits_begin, digits_end));
using size_type = typename static_string<N>::size_type;
static_string<N> result;
result.resize_and_overwrite(
N,
[&](char* buffer, size_type) -> size_type
{
char* const digits_end = buffer + N;
char* const digits_begin = integer_to_string<std::char_traits<char>, Integer>(
digits_end, value, std::is_signed<Integer>{});
const size_type len = digits_end - digits_begin;
std::char_traits<char>::move(buffer, digits_begin, len);
return len;
}
);

return result;
}

#ifdef BOOST_STATIC_STRING_HAS_WCHAR
Expand All @@ -562,11 +574,21 @@ inline
static_wstring<N>
to_static_wstring_int_impl(Integer value) noexcept
{
wchar_t buffer[N];
const auto digits_end = std::end(buffer);
const auto digits_begin = integer_to_wstring<std::char_traits<wchar_t>, Integer>(
digits_end, value, std::is_signed<Integer>{});
return static_wstring<N>(digits_begin, std::distance(digits_begin, digits_end));
using size_type = typename static_wstring<N>::size_type;
static_wstring<N> result;
result.resize_and_overwrite(
N,
[&](wchar_t* buffer, size_type) -> size_type
{
wchar_t* const digits_end = buffer + N;
wchar_t* const digits_begin = integer_to_wstring<std::char_traits<wchar_t>, Integer>(
digits_end, value, std::is_signed<Integer>{});
const size_type len = digits_end - digits_begin;
std::char_traits<wchar_t>::move(buffer, digits_begin, len);
return len;
}
);
return result;
}
#endif

Expand All @@ -591,59 +613,73 @@ inline
static_string<N>
to_static_string_float_impl(double value) noexcept
{
using size_type = typename static_string<N>::size_type;
// we have to assume here that no reasonable implementation
// will require more than 2^63 chars to represent a float value.
const long long narrow =
static_cast<long long>(N);
// extra one needed for null terminator
char buffer[N + 1];
// we know that a formatting error will not occur, so
// we assume that the result is always positive
if (std::size_t(std::snprintf(buffer, N + 1, "%f", value)) > N)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
std::snprintf(buffer, N + 1, "%.*e", precision, value);
}
// this will not throw
return static_string<N>(buffer);
static_string<N> result;
result.resize_and_overwrite(
N,
[&](char* buffer, size_type) -> size_type
{
// we know that a formatting error will not occur, so
// we assume that the result is always positive
std::size_t length = std::snprintf(buffer, N + 1, "%f", value);
if (length > N)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
length = std::snprintf(buffer, N + 1, "%.*e", precision, value);
}
return length;
}
);
return result;
}

template<std::size_t N>
inline
static_string<N>
to_static_string_float_impl(long double value) noexcept
{
using size_type = typename static_string<N>::size_type;
// we have to assume here that no reasonable implementation
// will require more than 2^63 chars to represent a float value.
const long long narrow =
static_cast<long long>(N);
// extra one needed for null terminator
char buffer[N + 1];
// snprintf returns the number of characters
// that would have been written
// we know that a formatting error will not occur, so
// we assume that the result is always positive
if (std::size_t(std::snprintf(buffer, N + 1, "%Lf", value)) > N)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<long double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
std::snprintf(buffer, N + 1, "%.*Le", precision, value);
}
// this will not throw
return static_string<N>(buffer);
static_string<N> result;
result.resize_and_overwrite(
N,
[&](char* buffer, size_type)->size_type
{
// snprintf returns the number of characters
// that would have been written
// we know that a formatting error will not occur, so
// we assume that the result is always positive
std::size_t length = std::snprintf(buffer, N + 1, "%Lf", value);
if (length > N)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<long double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
length = std::snprintf(buffer, N + 1, "%.*Le", precision, value);
}
return length;
}
);
return result;
}

#ifdef BOOST_STATIC_STRING_HAS_WCHAR
Expand All @@ -652,73 +688,85 @@ inline
static_wstring<N>
to_static_wstring_float_impl(double value) noexcept
{
using size_type = typename static_wstring<N>::size_type;
// we have to assume here that no reasonable implementation
// will require more than 2^63 chars to represent a float value.
const long long narrow =
static_cast<long long>(N);
// extra one needed for null terminator
wchar_t buffer[N + 1];
// swprintf returns a negative number if it can't
// fit all the characters in the buffer.
// mingw has a non-standard swprintf, so
// this just covers all the bases. short
// circuit evaluation will ensure that the
// second operand is not evaluated on conforming
// implementations.
const long long num_written =
std::swprintf(buffer, N + 1, L"%f", value);
if (num_written < 0 ||
num_written > narrow)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
std::swprintf(buffer, N + 1, L"%.*e", precision, value);
}
// this will not throw
return static_wstring<N>(buffer);
static_wstring<N> result;
result.resize_and_overwrite(
N,
[&](wchar_t* buffer, size_type) -> size_type
{
// swprintf returns a negative number if it can't
// fit all the characters in the buffer.
// mingw has a non-standard swprintf, so
// this just covers all the bases. short
// circuit evaluation will ensure that the
// second operand is not evaluated on conforming
// implementations.
long long num_written =
std::swprintf(buffer, N + 1, L"%f", value);
if (num_written < 0 ||
num_written > narrow)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
num_written = std::swprintf(buffer, N + 1, L"%.*e", precision, value);
}
return num_written;
}
);
return result;
}

template<std::size_t N>
inline
static_wstring<N>
to_static_wstring_float_impl(long double value) noexcept
{
using size_type = typename static_wstring<N>::size_type;
// we have to assume here that no reasonable implementation
// will require more than 2^63 chars to represent a float value.
const long long narrow =
static_cast<long long>(N);
// extra one needed for null terminator
wchar_t buffer[N + 1];
// swprintf returns a negative number if it can't
// fit all the characters in the buffer.
// mingw has a non-standard swprintf, so
// this just covers all the bases. short
// circuit evaluation will ensure that the
// second operand is not evaluated on conforming
// implementations.
const long long num_written =
std::swprintf(buffer, N + 1, L"%Lf", value);
if (num_written < 0 ||
num_written > narrow)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<long double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
std::swprintf(buffer, N + 1, L"%.*Le", precision, value);
}
// this will not throw
return static_wstring<N>(buffer);
static_wstring<N> result;
result.resize_and_overwrite(
N,
[&](wchar_t* buffer, size_type) -> size_type
{
// swprintf returns a negative number if it can't
// fit all the characters in the buffer.
// mingw has a non-standard swprintf, so
// this just covers all the bases. short
// circuit evaluation will ensure that the
// second operand is not evaluated on conforming
// implementations.
long long num_written =
std::swprintf(buffer, N + 1, L"%Lf", value);
if (num_written < 0 ||
num_written > narrow)
{
// the + 4 is for the decimal, 'e',
// its sign, and the sign of the integral portion
const int reserved_count =
(std::max)(2, count_digits(
std::numeric_limits<long double>::max_exponent10)) + 4;
const int precision = narrow > reserved_count ?
N - reserved_count : 0;
// switch to scientific notation
num_written = std::swprintf(buffer, N + 1, L"%.*Le", precision, value);
}
return num_written;
}
);
return result;
}
#endif

Expand Down Expand Up @@ -925,6 +973,7 @@ class basic_static_string
private:
template<std::size_t, class, class>
friend class basic_static_string;

public:
//--------------------------------------------------------------------------
//
Expand Down Expand Up @@ -3708,6 +3757,31 @@ class basic_static_string
size_type n,
value_type c);

/**
Resize the string and overwrite its contents.

Resizes the string to contain `n` characters, and uses the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that is useful, the size passed here doesn't really matter, does it? Maybe a write_and_resize(op) is more useful? Or is there any realistic use case for this param being less than N?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how the function is specified for std::string.

For static_string specifically the only thing the passed size does is throw if it's too big.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense

provided function object `op` to overwrite the string contents.
The function object is called with two arguments: a pointer to
the string internal buffer, and the size of the string. The
function object shall return the number of characters written to
the buffer, which shall be less than or equal to `n`. The string
size is set to the value returned by the function object.

@par Exception Safety

Strong guarantee. However, if an exception is thrown by
`std::move(op)(p, count)`, the behavior is undefined.

@throw std::length_error `n > max_size()`
*/
template<typename Operation>
BOOST_STATIC_STRING_CPP14_CONSTEXPR
void
resize_and_overwrite(
size_type n,
Operation op);

/** Swap two strings.

Swaps the contents of the string and `s`.
Expand Down Expand Up @@ -6658,6 +6732,26 @@ resize(size_type n, value_type c)
term();
}

template<std::size_t N, typename CharT, typename Traits>
template<typename Operation>
BOOST_STATIC_STRING_CPP14_CONSTEXPR
void
basic_static_string<N, CharT, Traits>::
resize_and_overwrite(
size_type n,
Operation op)
{
if (n > max_size()) {
detail::throw_exception<std::length_error>("n > max_size() in resize_and_overwrite()");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the message "n > max_size()" (identical to what's thrown in resize()) is sufficient. there's no need to proliferate string literals.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it sufficient? It gives no clue to the user about where the problem occurred.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe detail::throw_exception uses BOOST_THROW_EXCEPTION under the hood, which would also print the source location. But generally, the exception message doesn't need to include the current function name, "n > max_size()" is descriptive enough for this kind of error message in the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOOST_THROW_EXCEPTION, if used inside detail::throw_exception, would emit the source location of detail::throw_exception.

You probably want boost::throw_with_location here, or the two argument overload of boost::throw_exception, so that the source location of the throw is captured, not that of detail::throw_exception.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gennaroprota, it might be more convenient to create an issue for this and address it in a separate PR once you’re done with the current one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if BOOST_STATIC_STRING_STANDALONE is defined?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this configuration is to still be supported (is it?) you'd probably need to define BOOST_STATIC_STRING_CURRENT_LOCATION and then use detail::throw_exception( E(...), BOOST_STATIC_STRING_CURRENT_LOCATION ) instead of boost::throw_exception( E(...), BOOST_CURRENT_LOCATION ).

Something better might also be possible, I'm not sure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this configuration is to still be supported (is it?)

I'd be happy to remove the support. The less conditional compilation, the better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably ask on the list.

}

CharT* p = data();
const auto new_size = std::move(op)(p, n);
BOOST_STATIC_STRING_ASSERT(new_size >= 0 && size_type(new_size) <= n);
this->set_size(size_type(new_size));
term();
}

template<std::size_t N, typename CharT, typename Traits>
BOOST_STATIC_STRING_CPP14_CONSTEXPR
void
Expand Down
Loading
Loading