3
\$\begingroup\$

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';
}
  1. Does my implementation have any flaws? Can it fail in some cases? How would you write it to make it better?
  2. 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 empty optional since -100 is out of range of unsigned int.
asked Feb 12, 2022 at 9:21
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

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_casted 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.

answered Feb 12, 2022 at 10:21
\$\endgroup\$
2
  • \$\begingroup\$ I deleted the const char* const overload since I wanted to prevent the user from passing in non-null terminated strings. Also I think char* overload is less efficient since it calls strlen() to determine the end of the string. And it fails when there is no '0円' at the right position. \$\endgroup\$ Commented 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 the string_view. \$\endgroup\$ Commented Feb 12, 2022 at 11:10

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.