Can you makes the evil goto clean?

I've decided for future use to come up with a generic console input function. I needed a quick way to escape a nested if to the "failure" case.

I will clean this code up in the morning because it's pretty late but I'm just intrigued, is this code necessarily evil?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template<typename T>
T GetConsoleInput(std::string s)
{
    bool valid = 0;
    T value;

    while(!valid)
    {
        std::string input;
        std::cout << s; // Display prompt message
        std::getline(std::cin,input); // Get the whole line from the user
        std::stringstream ss;
        ss << input;
        if(ss >> value) // Try converting string to wanted type
        {
            std::stringstream str;
            str << value;
            if(str.str() == input) valid = 1; // This is to stop string stream accepting part of the string 12abc != 12
            else goto HORRIBLE; // Need a better escape to notify failure
        }
        else
        {
            HORRIBLE:
            std::cout << "Invalid input.\n";
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
            std::cin.clear();
            ss.str(std::string());
        }
    }
    return value;
}


Edit: I used to do a lot of this stuff while writing parts of my hobby operating system, but I know it has no real cause in higher level languages.
Last edited on
You could call a handle_invalid_input() function, but in this case you may to change your validation
1
2
3
4
5
6
std::istringstream ss(input);
if( ss>>value and ss.eof() )
  return value;
else{
  //Invalid
}



Also
1
2
3
4
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
            std::cin.clear();
            ss.str(std::string());
¿what are you doing? `cin' is not in an invalid state because you used the string stream, you are ignoring the next line of input, and the stringstream will be created again in line 12.
Last edited on
Thanks for the reply,

I have never used an istringstream before, I will have to look that up.

As for the later code:

You are totally right. I had adapted the code using stringstream instead of cin. I really should stop coding after 10PM. Tired eyes completely swept through that thanks for the input.

However in it's defense the program worked as intended every time I tested it. My main wonder of this code wasn't much it's design, as I stated I would clean it up in the morning. However curiosity to the using of goto in this particular case being necessarily bad code? I'm not sure if would simply translate to an assembly jmp instruction, making it slightly more efficient to a function call in this kind of case? These kind of things were almost always necessary when programming at a lower level. I'm just asking myself if the simplicity of goto could be used correctly in certain cases, rather than creating an entirely new function.

EDIT:

Thanks for the tips ne555, nice check on checking if the stream as reached eof. Nice one (Y).

Much better:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T>
T GetConsoleInput(std::string s)
{
    T value;
    while(1)
    {
        std::cout << s; // Display prompt message
        std::string input;
        std::getline(std::cin,input); // Get user input
        std::istringstream ss(input);
        if(ss >> value && ss.eof()) return value;
        else std::cout << "Invalid input.\n";
    }
}
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
#include <type_traits>

template < typename T > T& get_stdin_input( T& value, const std::string& prompt )
{
    std::cout << prompt << ": " ;

    std::string line ;
    if( std::getline( std::cin, line ) )
    {
        std::istringstream stm(line) ;
        char crud ;
        if( stm >> value && !( stm >> crud ) ) return value ;
    }
    else throw std::runtime_error( "input failure on stdin" ) ;

    std::cout << "invalid input. try again\n";
    return get_stdin_input( value, prompt );
}

std::string& get_stdin_input( std::string& value, const std::string& prompt )
{
    std::cout << prompt << ": " ;
    if( std::getline( std::cin, value ) ) return value ;
    else throw std::runtime_error( "input failure on stdin" ) ;
}

template < typename T > T get_stdin_input( const std::string& prompt )
{
    static_assert( std::is_default_constructible<T>::value, "must be a default constructible type" ) ;
    T value ;
    return get_stdin_input( value, prompt );
}
JLBorges - You never cease to amaze me.

return get_stdin_input( value, prompt );

That recursive returning has just blown my mind.

I am struggling to follow this:

1
2
3
4
5
6
template < typename T > T get_stdin_input( const std::string& prompt )
{
    static_assert( std::is_default_constructible<T>::value, "must be a default constructible type" ) ;
    T value ;
    return get_stdin_input( value, prompt );
}


It is 20 past 4 in the morning so I will have to take a proper look at this after some sleep.
Topic archived. No new replies allowed.