I want to make the following function a function template that supports all the integral types:
#include <iostream>
#include <utility>
#include <charconv>
#include <string_view>
#include <concepts>
#include <limits>
#include <optional>
// header file
std::optional<int> to_integer( std::string_view token, const std::pair<int, int> acceptableRange =
{ std::numeric_limits<int>::min( ), std::numeric_limits<int>::max( ) } ) noexcept;
std::optional<int> to_integer( const char* const token, const std::pair<int, int> acceptableRange =
{ std::numeric_limits<int>::min( ), std::numeric_limits<int>::max( ) } ) noexcept = delete;
// source file
std::optional<int> to_integer( std::string_view token, const std::pair<int, int> acceptableRange ) noexcept
{
if ( token.empty( ) )
{
return { };
}
if ( token.size( ) > 1 && token[ 0 ] == '+' && token[ 1 ] != '-' ) { token.remove_prefix( 1 ); }
int value;
const auto [ ptr, ec ] { std::from_chars( token.begin( ), token.end( ), value, 10 ) };
const auto& [ minAcceptableValue, maxAcceptableValue ] { acceptableRange };
if ( ec != std::errc( ) || ptr != token.end( ) ||
value < minAcceptableValue || value > maxAcceptableValue ) { return { }; }
return value;
}
Here is my version:
// header file
template < std::integral T >
std::optional<T> to_integer( std::string_view token, const std::pair<T, T> acceptableRange =
{ std::numeric_limits<T>::min( ), std::numeric_limits<T>::max( ) } ) noexcept;
template < std::integral T >
std::optional<T> to_integer( const char* const token, const std::pair<T, T> acceptableRange =
{ std::numeric_limits<T>::min( ), std::numeric_limits<T>::max( ) } ) noexcept = delete;
// source file
template < std::integral T >
std::optional<T> to_integer( std::string_view token, const std::pair<T, T> acceptableRange ) noexcept
{
if ( token.empty( ) )
{
return { };
}
if ( token.size( ) > 1 && token[ 0 ] == '+' && token[ 1 ] != '-' ) { token.remove_prefix( 1 ); }
T value;
const auto [ ptr, ec ] { std::from_chars( token.begin( ), token.end( ), value, 10 ) };
const auto& [ minAcceptableValue, maxAcceptableValue ] { acceptableRange };
if ( ec != std::errc( ) || ptr != token.end( ) ||
value < minAcceptableValue || value > maxAcceptableValue ) { return { }; }
return value;
}
// call site
int main( )
{
const std::string_view sv { "-100" };
const auto retVal { to_integer<unsigned int>( sv, { 0, 100 } ) };
std::cout << retVal.value_or( -1 ) << '\n';
}
- Does my implementation have any flaws? Can it fail in some cases? How would you write it to make it better?
- It somehow returns 4294967295 when passing e.g. "-100" as can be seen in the
main
function. Why? How can I fix that? I expect it to return an emptyoptional
since -100 is out of range ofunsigned int
.
1 Answer 1
Don't delete the overload that takes a const char*
I don't understand why you delete the overload that takes a const char*
as the first parameter. If you don't delete it, the one that takes a std::string_view
is a viable candidate, because there is an implicit conversion from const char*
to std::string_view
. And it does exactly what you want.
Consider removing the check for a leading +
-sign
I would remove the check for a leading +
-sign, and rely solely on std::from_chars()
to determine the validity of the input. This makes your function behave more like other standard library functions, and thus avoids potentially surprising behavior. If you do need such functionality, consider putting it into a separate function, so the caller can choose whether to make use of that or not.
The case of the return value 4294967295
This is not because of any flaw in to_integer()
, rather it is because you are using std::optional
's value_or()
. The int
-1
you pass in will be static_cast
ed to the value type of the optional before it is returned. Since the value type is unsigned int
, the -1
will be converted to 4294967295
.
-
\$\begingroup\$ I deleted the
const char* const
overload since I wanted to prevent the user from passing in non-null terminated strings. Also I thinkchar*
overload is less efficient since it callsstrlen()
to determine the end of the string. And it fails when there is no '0円' at the right position. \$\endgroup\$digito_evo– digito_evo2022年02月12日 11:07:52 +00:00Commented Feb 12, 2022 at 11:07 -
\$\begingroup\$ "Consider removing the check for a leading +-sign", but I don't quite get why I should remove it.
from_chars
does not recognize the+
character. So I have to remove it from thestring_view
. \$\endgroup\$digito_evo– digito_evo2022年02月12日 11:10:52 +00:00Commented Feb 12, 2022 at 11:10