An interesting obstacle with a generic console input

Thanks to a lot of help the other day I have a nice generic layout for getting console input ( Sorry JLBorges, I never did understand that "is_constructible"). I have edited the code to try and eliviate some memory usage when the user enters something wrong:

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
// Recursive function returns itself on failure (Omits while loop)
template<typename T>
T& GetConsoleInput(T& value, const std::string& s)
{
    static std::string input; // Static stops multiple objects being created and
    static char extra;        // pushed to the stack on failure due to recursion
    static std::istringstream* ss; // Had to new due to bug (Not accepting legit value after multiple failures with static alone
    std::cout << s << ": "; // Display prompt message
    if(std::getline(std::cin,input)) // Get user input
    {
        ss = new std::istringstream(input);
        if(*ss >> value && !(*ss >> extra))
        {
            delete ss; return value; // If value is of correct type and end of stream is reached
        }
    }
    else throw std::runtime_error("Error: Input failure on stdin.");
    std::cout << "Invalid input. Try again.\n"; delete ss;
    return GetConsoleInput<T>(value,s); // Call itself continuously until it get's a correct input
}

// Cleaner efficient code for just strings
std::string& GetConsoleInput(std::string& value, const std::string& s)
{
    std::cout << s << ": "; // Display prompt message
    if(std::getline(std::cin,value)) return value;
    else throw std::runtime_error("Error: Input failure on stdin.");
}

// This is the base function the user calls
template<typename T>
T GetConsoleInput(const std::string s)
{
    T value;
    return GetConsoleInput(value,s);
}



How would I return multiple values? If say, I wanted the user to enter 2 int's I could easily return a vector of type T.

But what happens if I want them to enter say, an int, followed by a double, followed by a string? I cannot have a vector that can store multiple types. My only other suggestion would be to create a base class with all elementary values and return a vector of these classes, and hope the user knowns which value to get from each element in the vector. But i think that's pretty ugly.

Anyone got any idea's? Not looking for code as such ( but will be appreciated ) just general tips or a nudge in the right direction will help me out.

Thanks in advance.

EDIT: Spelling mistakes.

UPDATE:

I tried to be clever trying to overload the istream operator>> in a class, to see if that woked:

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
#include <iostream>
#include <string>

#include "D:\mega\mega.h"

class c
{
private:
    int i;
    double d;
    std::string s;
public:
    c() {}
    std::istream& operator>>(std::istream& is)
    {
        is >> i >> d >> s;
        return is;
    }
    void display() { std::cout << i << d << s; }
};

using namespace mega;

int main()
{
    int a = GetConsoleInput<int>("Enter an integer");
    ConsolePrint("The integer you entered was ",a,"\n");
    c a_class = GetConsoleInput<c>("Enter an integer, followed by a double, then a string: ");
    a_class.display();
    return 0;
}


However I get the error:

||=== Build: Debug in Functions (compiler: GNU GCC Compiler) ===|
D:\mega\mega.h||In instantiation of 'T& mega::GetConsoleInput(T&, const string&) [with T = c; std::string = std::basic_string<char>]':|
D:\mega\mega.h|48|required from 'T mega::GetConsoleInput(std::string) [with T = c; std::string = std::basic_string<char>]'|
main.cpp|26|required from here|
D:\mega\mega.h|25|error: cannot bind 'std::basic_istream<char>' lvalue to 'std::basic_istream<char>&&'|
|error: initializing argument 1 of 'std::basic_istream<_CharT, _Traits>& std::operator>>(std::basic_istream<_CharT, _Traits>&&, _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = c]'|
||=== Build failed: 2 error(s), 3 warning(s) (0 minute(s), 0 second(s)) ===|
Last edited on
I have edited the code to try and eliviate some memory usage when the user enters something wrong:

If you wanted to alleviate memory usage, removing the recursion would be the best thing you could do. Also removing the gratuitous use of new/delete would be good.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename T>
void GetConsoleInput(T& value, const std::string& prompt)
{
    std::string line;
    std::istringstream iss;

    while (std::cout << prompt, getline(std::cin, line))
    {
        iss.clear();            // clear error state
        iss.str(line);          // set stream contents

        char extra;
        if (iss >> value >> std::ws && !(iss >> extra))
            return;

        std::cout << "Invalid input.\n";
    }

    throw std::runtime_error("Error: Input failure on stdin.");
}


But what happens if I want them to enter say, an int, followed by a double, followed by a string?

In general, I think this is the wrong approach for input from the console, but the following link may be of interest.
http://www.boost.org/doc/libs/1_60_0/doc/html/any.html


However I get the error:

In a method of a class, the first argument is always the this argument. The stream insertion and extraction operators require the first argument to be a reference to the stream object, thus defining these operators as members of a class is not advisable.
Thanks for the reply cire, i am sorry for the late reply other things have kept me away from the computer these past couple of days. Also, thanks for that spelling correction!

First off,

On my first approach I didn't use recursion, I used a while loop as you suggested albeit not as elegant. I found the recurrsion nice and something different to what I normally do so I incorporated JLBorges design. Thew new and delete I would like to omit anyway, when I used it without static it worked fine without new/delete, but something kept happening after when using static that wouldn't accept correct input after 50 or so concurrent failures.

I must ask though: while (std::cout << prompt, getline(std::cin, line)) I have never used a comma between two expressions in a while loop before, how is that being evaluated? As two separate expressions?

Secondly:
In general, I think this is the wrong approach for input from the console


I am always willing to take onboard new idea's, could you elaborate? Suggest an alternative? I am creating this for a nice framework becuse most of my hobby coding I do in the console. It would be nice not having to keep writing the input and output error checking and having it all nicely handled.

Thirdly,

Duely noted, I'll look this up further and fix it now. Thanks for the information.



On a different note,

Once I have all of this working, I would like to create a way to not only make sure the input is valid for the type of data wanted, but also to make sure it is valid against a user defined range, such as:

Enter an integer (1-100): 


I think I am on the write path by creating another derative of the function, adding a std::function<bool> argument at the end, then checking the valid input data against this function, if success return the value, if not get input again.

Is this right?

EDIT: Spelling and reworded a little.
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
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

template < typename T > T& GetConsoleInput( const std::string& prompt, T& value )
{
    std::string input;
    std::cout << prompt << ": ";
    if( std::getline(std::cin,input) )
    {
        std::istringstream stm(input);
        char extra;
        if( stm >> value && !( stm >> extra ) )  return value;
    }
    else throw std::runtime_error("Error: Input failure on stdin.");
    std::cout << "Invalid input. Try again.\n";
    return GetConsoleInput<T>(prompt,value);
}

void GetConsoleInput( std::vector<std::string> ) {}

template < typename FIRST, typename... REST >
void GetConsoleInput( std::vector<std::string> prompts, FIRST& first, REST&... rest   )
{
    if( prompts.empty() ) prompts.push_back( "?" ) ;
    GetConsoleInput( prompts.front(), first ) ;
    GetConsoleInput( std::vector<std::string>{ prompts.begin()+1, prompts.end() }, rest... ) ;
}

int main()
{
    int i ;
    double d ;
    std::string s ;
    GetConsoleInput( { "an integer", "a floating point value", "and finally, a string" }, i, d, s ) ;
    std::cout << '\n' << i << ' ' << d << ' ' << s << '\n' ;
}

http://rextester.com/VSFFG33646
Last edited on
Why does your code always make me uncotrollable grin all the time. It looks like an adapted version of the generic console output I've got, except you've added a vector of prompts and you just call the inital function with the string stream to get the correct type's.

Genius.



EDIT:

Here is my final code, over time this is going to save me hours of typing. Thanks for all the help all.

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#ifndef MEGA_H
#define MEGA_H

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <stdexcept>
#include <vector>

namespace mega
{

template<typename T>
void GetConsoleInput(const std::string& s, T& value)
{
    std::string input;
    std::istringstream ss;

    while(std::cout << s << ": ", std::getline(std::cin,input))
    {
        ss.clear();
        ss.str(input);
        char extra;
        if(ss >> value && !(ss >> extra)) return; // If value is of correct type and end of stream is reached
        std::cout << "Invalid input. Try again.\n";
    }
    throw std::runtime_error("Error: Input failure on stdin.");
}
// Cleaner efficient code for just strings
std::string& GetConsoleInput(std::string& value, const std::string& s)
{
    std::cout << s << ": ";
    if(std::getline(std::cin,value)) return value;
    throw std::runtime_error("Error: Input failure on stdin.");
}
// This is the base function the user calls
template<typename T>
T GetConsoleInput(const std::string s)
{
    T value;
    GetConsoleInput(s,value);
    return value;
}

// End of recursion
void GetConsoleInput(std::vector<std::string> prompts) {}

template<typename FIRST, typename... REST>
void GetConsoleInput(std::vector<std::string> prompts, FIRST& f, REST&... r)
{
    if(prompts.empty()) prompts.push_back("?");
    GetConsoleInput(prompts.front(),f);
    GetConsoleInput(std::vector<std::string>(prompts.begin()+1,prompts.end()),(r)...);
}

// This function is called when there are no more parameters
void ConsolePrint() {}

// Prints the first parameter and calls itself s gain until no more
template<typename T, typename... ARGS>
void ConsolePrint(T arg, ARGS... args)
{
    std::cout << arg;
    ConsolePrint((args)...);
}

} // END MEGA NAMESPACE

#endif // MEGA_H


// MAIN

#include <iostream>
#include <string>

#include "D:\mega\mega.h"

using namespace mega;

class C
{
private:
    int i;
    double d;
    std::string s;
public:
    C() {}
    void display() { std::cout << "Integer: " << i << " Double: " << d << " String: " << s << "\n"; }
    friend std::istream& operator>>(std::istream& is, C& c);
};

std::istream& operator>>(std::istream& is, C& c)
{
    is >> c.i >> c.d >> c.s;
    return is;
}

int main()
{
    int a = GetConsoleInput<int>("Enter an integer");
    ConsolePrint("The integer you entered was ",a,"\n");
    C a_class = GetConsoleInput<C>("Enter an integer, followed by a double, then a string: ");
    a_class.display();
    int z = 0;
    GetConsoleInput("Enter an integer",z);
    ConsolePrint(z,"\n");
    GetConsoleInput({"Enter an integer","Enter another integer"},a,z);
    ConsolePrint("A =",a,"\tAnd Z = ", z,"\n");
    return 0;
}
Last edited on
Topic archived. No new replies allowed.