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.
\$\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.

\$\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\$
    – digito_evo
    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\$
    – digito_evo
    Feb 12, 2022 at 11:10

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.