Cleaner way to handle incorrect input

Hi all,

Currently, the way I'm handling input is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
   int aNumber{0};

   std::cout << "Enter a number: ";
   while(!(std::cin >> aNumber))
   {
      std::cin.clear();
      std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
      std::cout << "Enter a number: ";
   }
   return 0;
}


This way if they input anything that causes the input stream to fail, I catch it, clear it, ignore the rest of the stream and reprompt.

This results in a lot of duplicate prompts and repetitive code EVERY time I accept input from the user. Is there a cleaner way to do this? Could I create my own input stream that wraps std::cin and handles stream failure?
> This results in a lot of duplicate prompts and repetitive code EVERY time I accept input from the user.
> Could I create my own input stream that wraps std::cin and handles stream failure?

Your own functions. For instance:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <string>
#include <limits>
#include <type_traits>

namespace utility
{
    template < typename T > T& get_value( T& value, const std::string& prompt = "?"  )
    {
        std::cout << prompt << ' ' ;
        if( std::cin >> value ) return value ;

        std::cerr << "bad input.\n" ;
        std::cin.clear();
        std::cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
        return get_value( value, prompt ) ;
    }

    template < typename T > T get_value( const std::string& prompt = "?" )
    {
        static_assert( std::is_default_constructible<T>::value, "not DefaultConstructible" ) ;
        static_assert( std::is_constructible<T,T&&>::value, "not copyable or moveable" ) ;

        T value ;
        get_value( value, prompt ) ;
        return value ;
    }

    template < typename T, typename FN >
    T& get_value( T& value, FN is_valid, const std::string& prompt = "?"  )
    {
        get_value( value, prompt ) ;
        if( is_valid(value) ) return value ;

        std::cerr << "invalid value\n" ;
        return get_value( value, is_valid, prompt ) ;

    }

    template < typename T, typename FN >
    T get_value( FN is_valid, const std::string& prompt = "?"  )
    {
        static_assert( std::is_default_constructible<T>::value, "not DefaultConstructible" ) ;
        static_assert( std::is_constructible<T,T&&>::value, "not copyable or moveable" ) ;

        T value ;
        get_value<T>( value, is_valid, prompt ) ;
        return value ;
    }
}

int main()
{
    const int v = utility::get_value<int>( []( int v ) { return v%2 == 1 ; }, "enter an odd integer:" ) ;
    std::cout << v << '\n' ;
}
Great suggestion!

For some reason when I implement it and use it like this:

returnValue = Utility::acceptValue<int>("Please enter the die you'd like to hold (-1 to re-reroll): ");

I get an error

term does not evaluate to a function taking 1 arguments
Can't suggest anything without seeing the code for Utility::acceptValue<>
Sorry about that.

Here's the code. I think it's just a renamed version of the code you gave me. But perhaps I made a typo?

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
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <string>
#include <limits>
#include <type_traits>

namespace Utility
{
	template<typename T>
	T& acceptValue(T &value, const std::string &prompt = "?")
	{
		std::cout << prompt;
		if (std::cin >> value) return value;

		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
		acceptValue(value, prompt);
	}

	template<typename T>
	T& acceptValue(const std::string &prompt = "?")
	{
		T value;
		return accept_value(value, prompt);
	}

	template < typename T, typename FN >
	T& acceptValue(T& value, FN is_valid, const std::string& prompt = "?")
	{
		acceptValue(value, prompt);
		if (is_valid(value)) return value;

		std::cerr << "invalid value\n";
		return acceptValue(value, is_valid, prompt);
	}

	template < typename T, typename FN >
	T& acceptValue(FN is_valid, const std::string& prompt = "?")
	{
		static_assert(std::is_default_constructible<T>::value, "not DefaultConstructible");
		static_assert(std::is_constructible<T, T&&>::value, "not copyable or moveable");

		T value;
		acceptValue<T>(value, is_valid, prompt);
		return value;
	}
}
Line 23, is that supposed to be acceptValue instead?
Yes, my mistake. Sorry about that. That was an error in the code, but unfortunately not the one that is causing the aforementioned error. I'm thinking the invocation of

returnValue = Utility::acceptValue<int>("Please enter the die you'd like to hold (-1 to re-reroll): ");

is being matched to the wrong template function? Because certainly that previous code matches to this function:

1
2
3
4
5
6
7
	template<typename T>
	T& acceptValue(const std::string &prompt = "?")
	{
		T value;
		return acceptValue(value, prompt);
	}


but the error seems like it's being matched to this function:

1
2
3
4
5
6
7
8
9
template < typename T, typename FN >
	T& acceptValue(T& value, FN is_valid, const std::string& prompt = "?")
	{
		acceptValue(value, prompt);
		if (is_valid(value)) return value;

		std::cerr << "invalid value\n";
		return acceptValue(value, is_valid, prompt);
	}


When I click on the error, it takes me to the line if(is_valid(value)) return value;
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <string>
#include <limits>
#include <type_traits>

namespace Utility
{
        template<typename T>
	T& acceptValue(T &value, const std::string &prompt = "?")
	{
		std::cout << prompt;
		if (std::cin >> value) return value;

		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
		// acceptValue(value, prompt);
                return acceptValue(value, prompt); // ***
	}

	template<typename T>
	// T& acceptValue(const std::string &prompt = "?") // *** can't return reference here
        T acceptValue(const std::string &prompt = "?") // ***
	{
		T value;
		// return accept_value(value, prompt);
                return acceptValue(value, prompt); // ***
	}

	template < typename T, typename FN >
	T& acceptValue(T& value, FN is_valid, const std::string& prompt = "?")
	{
		acceptValue(value, prompt);
		if (is_valid(value)) return value;

		std::cerr << "invalid value\n";
		return acceptValue(value, is_valid, prompt);
	}

	template < typename T, typename FN >
	// T& acceptValue(FN is_valid, const std::string& prompt = "?") // *** can't return reference here
        T acceptValue(FN is_valid, const std::string& prompt ) // ***
	{
		static_assert(std::is_default_constructible<T>::value, "not DefaultConstructible");
		static_assert(std::is_constructible<T, T&&>::value, "not copyable or moveable");

		T value;
		acceptValue<T>(value, is_valid, prompt);
		return value;
	}
}

int main()
{
    const auto returnValue = Utility::acceptValue<int>( "Please enter the die you'd like to hold (-1 to re-reroll): ");
    std::cout << returnValue << '\n' ;
}

http://coliru.stacked-crooked.com/a/5543414eec7c28ed


> I'm thinking the invocation of ... is being matched to the wrong template function?

Yes. Remove the default value for the second parameter in the binary function.
Last edited on
Thanks buddy. I see what was happening here now. I shouldn't return by reference in those functions because the value is automatically created in the template functions and is destroyed after control leaves the function's scope.

Also, I think I see what I did wrong with the default parameter, as it caused a mismatch in the invocation.
Topic archived. No new replies allowed.