Search code examples
c++c++11templatesstdstdstring

C++ std::string to number template


I am currently trying to implement my own standard input reader for personal use. I have created a method to read an integer from standard input and do some checks on its validity. The idea is that I read a string from the standard input, do several checks, convert to int, do last checks, return the value that has been read. If any error happens meanwhile the checks I will just fill an errorHint to print on std::cerr and return std::numeric_limits<int>::min().

I think the idea is quite simple and straightforward to implement, now I wanted to generalize the concept and make the method template, so basically I could chose at compile time, whenever I need to read from the standard input which type of integer I want (it could be int, long, long long, unsigned long and so on but an integer). In order to do so I have created the following static template method:

template<
    class T,
    class = typename std::enable_if<std::is_integral<T>::value, T>::type
> 
static T getIntegerTest(std::string& strErrorHint,
                        T nMinimumValue = std::numeric_limits<T>::min(),
                        T nMaximumValue = std::numeric_limits<T>::max());

and the implementation in the same .hpp file few lines below:

template<
    class T,
    class>
T InputReader::getIntegerTest(std::string& strErrorHint,
                              T nMinimumValue,
                              T nMaximumValue)
{
    std::string strInputString;
    std::cin >> strInputString;

    // Do several checks

    T nReturnValue = std::stoi(strInputString); /// <--- HERE!!!

    // Do other checks on the returnValue

    return nReturnValue;
}

Now the problem is, I want to convert the string that I just read and that I know is within the correct range to the integer type T. How can I do this in a good way?


Solution

  • Specialising a function object is a very versatile way to modify behaviour based on type traits.

    The approach is:

    1. define a general template for the operation

    2. specialise the template for corner cases

    3. call through a helper function

    Example:

    #include <iostream>
    #include <type_traits>
    #include <string>
    
    
    namespace detail {
    /// general case
        template<class Integer, typename Enable = void>
        struct convert_to_integer {
            Integer operator()(std::string const &str) const {
                return std::stoi(str);
            }
        };
    
    // special cases
        template<class Integer>
        struct convert_to_integer<Integer, std::enable_if_t<std::is_same<long, Integer>::value> > {
            long operator()(std::string const &str) const {
                return std::stol(str);
            }
        };
    }
    
    template<class T, class StringLike>
    T to_integral(StringLike&& str)
    {
        using type = std::decay_t<T>;
        return detail::convert_to_integer<type>()(str);
    };
    
    int main() {
    
        std::string t1 = "6";
        const char t2[] = "7";
    
        std::cout << to_integral<int>(t1) << std::endl;
        std::cout << to_integral<int>(t2) << std::endl;
    
        // will use the specilaisation
        std::cout << to_integral<long>(t1) << std::endl;
        std::cout << to_integral<long>(t2) << std::endl;
    
        // will use the default case
        std::cout << to_integral<short>(t1) << std::endl;
        std::cout << to_integral<short>(t2) << std::endl;
    }
    

    p.s. your error reporting strategy needs work. Suggest throwing a std::runtime_error.