Because things, these days, seem a little 'slow' around this forum, please forebare this old post, (that was recently resurrected by an inquiring mind), and humour this 'update', in good faith that this update might be appreciated by some beginning students of C++ ... :)

// beginnerBadCppExampleCodeBeforeFixedUp.cpp // 2013-08-14 //

// This following 'Supposed example' of good code is ...
// re-worked ...
// to demo an acceptable coding style for current C++ students ...

#include<iostream.h>
#include<conio.h>

float no,price[50],total[50],qty[50],tt;
char item[30];

void main()
{
    int i,j;
    clrscr();

    cout<<"\n\n\t\t\tRaje's Billing Software\n\n";

    cout<<"\nEnter The Number of Products you'v Purchased:";
    cin>>no;

    cout<<"\n\nSL No\tITEM NAME\tQUANTITY\tPRICE/ITEM\n\n";
    for(i=1;i<=no;i++)
    {
        cout<<i;
        cout<<"\t";
        cin>>item;
        cout<<"\t";
        cin>>qty[i];
        cout<<"\t";
        cin>>price[i];
        total[i]=price[i]*qty[i];
        cout<<"\n\n";
    }

    for(i=1;i<=no;i++)
    {
        tt=tt+total[i];
    }

    cout<<"\n\n\t\t\tTOTAL:"<<tt<<" Rupees";
    cout<<"\n\n\t\t\tTHANK YOU VISIT AGAIN.....";
    getch();
}

As DaniWeb Nick Evans rightly indicated (a long time ago) ...

I'm sorry to tell you, but this code is just horrible;
people should never ever use this snippet.
1. void main() does not exist in standard C++
2. neither does iostream.h or conio.h.
They are only used by Turbo C which is only used by people
from India and the Philippines for some reason.
The rest of the world uses IDE's from this millennium.
3. Global variables. Yuck.
4. No indention. Double yuck.
5. neither getch() or clrsrc() are valid C++ functions
6. No error control on input what-so-ever."

Ok ...

here is how it might be re-worked ...

to get a 'pass' as acceptable student code 'these days' ...

// beginnerBadCppExampleCodeFixedUp.cpp // 2013-08-14 //

#include <iostream> // use <iosteam ... (NOT <iostream.h>)
#include <iomanip> // re. setw, precision, fixed
#include <string> // use C++ string unless C strings are REALLY needed //

//using namespaces std; // better to not use here to avoid 'name clashes'  //

const int MAX_ARY_SIZE = 2; // keep small while testing/debugging ... //


// can use an array of data struct's
// (or better, use a C++ STL vector, in place of a dynamic array)

struct Product
{
    // 3 data members in this data 'record' / data 'struct'
    std::string itemName;
    int numItems;
    double unitPrice; // in local units per item //

    // C++ struct (or class) can have functions/methods to process data
    // Note: total can be found from numItems and unitPrice ... so ...
    // NO need to store total (i.e. total is NOT independant input data)
    double get_total() const { return numItems * unitPrice; }

} ; // <-- note semi-colon to tell compiler 'struct is done' //


// could use here ... (to validate input) ...
// TWO functions for input, one for int and one for double ...
// but why not just use ONE  'template function' instead ...
template< typename T >
T takeIn( const std::string& msg, const T& min, const std::string& errMsg = "" )
{
    T val;
    while( true )
    {
        std::cout << msg << std::flush;
        if( std::cin >> val && std::cin.get() == '\n' && val >= min )
            break;
        else
        {
            std::cout << errMsg;
            std::cin.clear();
            std::cin.sync();
        }
    }
    return val;
}

// returns a non-empty string with CAPS on ALL first letters ...
// defaults to noEmpty ,,,
// if noEmpty set to false, then allows an empty string as valid input
std::string takeInString( const std::string& msg, bool noEmpty = true )
{
    std::string val;
    for( ; ; )
    {
        std::cout << msg << std::flush;
        getline( std::cin, val );
        size_t len = val.size();
        if( len )
        {
            val[0] = toupper( val[0] );
            size_t pos = 0;
            while( (pos = val.find( ' ', pos )) != std::string::npos )
            {
                ++pos;
                if( pos < len )
                    val[pos] = toupper( val[pos] );
                else break;
            }
        }
        // else ...
        if( !len && noEmpty )
            std::cout << "\nBlank line not valid here ...\n";
        else break;
    }

    return val;
}


char takeInChar( const std::string& msg )
{
    std::cout << msg << std::flush;
    std::string reply;
    getline( std::cin, reply );
    if( reply.size() )
        return reply[0];
    // else ...
    return 0;
}

bool more()
{
    if( tolower( takeInChar( "More (y/n) ? " )) == 'n' )
        return false;
    // else ...
    return true;
}



int main()
{
    Product cart[MAX_ARY_SIZE]; // get room to hold this many products

    using std::cout;
    cout << "\n\t\t\tRaje's Billing Software\n"
         << "\nBelow you will be asked to enter ...\n"
         << "\nITEM NAME,\nQUANTITY\nUNIT PRICE\n\nfor each item.\n";

    int no = 0; // inital num products in cart ... //
    do
    {
        cout << "\nFor item " << (no+1) << ", please enter ... \n";
        cart[no].itemName = takeInString( "Item name  : " );
        cart[no].numItems =
            takeIn< int >( "Quantity   : ", 1, "\nValid input is a numner >= 1\n" );
        cart[no].unitPrice =
            takeIn< double >( "Unit price : ", 1.0,
                              "\nValid input is a number >= 1.0\n" );
        cout << '\n';

        ++no;

        if( no == MAX_ARY_SIZE )
        {
            cout << "\nYou have reached the max cart size "
                 << "allowed here of " << no << " items.\n"
                 << "Proceeding to 'Check-Out' right now ...\n";
            break;
        }
    }
    while( more() );


    double sumTotal = 0.0;

    // NOTE! In C/C++ ... arrays start with index 0 ...
    using std::setw;
    cout << "\nYour cart ...\n"
         << setw( 20 ) << "Item"
         << setw( 15 ) << "Quanity"
         << setw( 15 ) << "Unit Price"
         << setw( 15 ) << "Line Total"
         << '\n';

    cout << std::fixed << std::setprecision( 2 );
    // show 2 decimal places in monet

    for( int i = 0; i < no; ++ i )
    {
        cout << ' ' << setw( 19 ) << cart[i].itemName
             << ' ' << setw( 14 ) << cart[i].numItems
             << ' ' << setw( 14 ) << cart[i].unitPrice
             << ' ' << setw( 14 ) << cart[i].get_total()
             << '\n';
        sumTotal += cart[i].get_total();
    }

    cout << "\n\nTOTAL: " << sumTotal << " local units of money."
         << "\n\nTHANK YOU FOR YOUR PURCHASE TODAY."
         << "\n\nPLEASE VISIT AGAIN SOON.\n";


    takeInString( "\nPress 'Enter' to continue/exit ... ", false );

}

Recommended Answers

All 9 Replies

Oops ... forgot to have ...

#include <cctype> // re. tolower, toupper

So please add to code above.

Member Avatar for iamthwee

One question... Why?

I'm not sure this is really what you should expect a beginner to produce... seems a bit too advanced. Anyways, here are a few nit-picking remarks.

1) They are not called "template functions", they are called "function templates". This is an easy slip of the tongue, but the latter (and grammatically correct) terminology is useful in stressing the character of templates as being instructions to the compiler on how to generate some code for a given set of compile-time parameters (template arguments: types, etc.). In this case, it is a template to instruct the compiler on how to generate a function for a specific type T, it is not a special kind of function.

2) toupper and tolower are in the std namespace (like all other standard functions and classes, even those coming from C libraries). You forgot to put the full qualification or using-clause.

3) There is no need to flush std::cout just before taking an input from std::cin. Taking an input from std::cin is guaranteed to trigger a flush of the standard output.

4) I suspect that you used the "min" parameter in your takeIn function template just so that you wouldn't have to specify the type T explicitly at the call-site. Although I see the convenience in that, I don't really agree we this kind of practice in general. One reason for that objection is that you end up compromising your interface for a rather trivial reason. And your takeIn function is a good example of that. Is it really always appropriate to have a "minimum value" for the input? What if T does not have a less-than comparison operator? What if you want a maximum value instead?

I would generally favor the following alternative:

namespace detail {

    template <typename T>
    bool get_from_cin(T& val) {
        std::cin >> val;
        return std::cin;
    };

    inline
    bool get_from_cin(std::string& s) {
        getline( std::cin, s );
        return std::cin;
    };

    inline
    bool get_from_cin(char& c) {
        std::string s;
        getline( std::cin, s );
        if( s.size() )
            c = s[0];
        return std::cin;
    };

};

template < typename T, typename Predicate >
T takeIn( const std::string& msg, Predicate pred, 
          const std::string& errMsg = "Invalid input!" )
{
    T result;
    while( true )
    {
        std::cout << msg;
        if( detail::get_from_cin(result) && pred(result) )
            break;
        else
        {
            std::cout << errMsg;
            std::cin.clear();
            std::cin.sync();
        }
    }
    std::cin.ignore();  // in case of remaining characters.
    return result;
}

template < typename T >
T takeIn( const std::string& msg )
{
    T result;
    while( true )
    {
        std::cout << msg;
        if( detail::get_from_cin(result) )
            break;
        else
        {
            std::cout << "Invalid Input!";
            std::cin.clear();
            std::cin.sync();
        }
    }
    std::cin.ignore();  // in case of remaining characters.
    return result;
}

You will also notice that this function can be used for the string input as well. It might seem trivial to reuse a simple function like that, but it is important to learn to reduce the redundancy of the code:

std::string takeInString( const std::string& msg, bool noEmpty = true )
{
    // first, re-use 'takeIn' to get the input string:
    std::string result = takeIn<std::string>( 
        msg, 
        [noEmpty](const std::string& s) {
            return !(noEmpty && (s.size() == 0));
        }, 
        "Blank line not valid here!");

    // then, do the 'toupper' transform:
    bool last_was_space = true;
    for( char& c : result ) 
    {
        if( last_was_space )
            c = std::toupper(c);
        last_was_space = ( c == ' ' );
    }
    return result;
}

inline
bool more()
{
    return ( std::tolower( takeIn<char>( "More (y/n)?" ) ) == 'y' );
}

You could even lump that last part of the takeInString function into a separate function.

The inputs would be taken as so:

cart[no].itemName = takeInString( "Item name  : " );
cart[no].numItems = takeIn< int >( "Quantity   : ", 
  [](int i) { return i > 0; }, 
  "\nThe number of items must be greater than 0!\n" );
cart[no].unitPrice = takeIn< double >( "Unit price : ", 
  [](double d) { return d > 0.0; },
  "\nAn item's unit price cannot be negative!\n" );

5) If you are going to recommend something to students, you should recommend that they document their interfaces well too:

/**
 * This function template takes an input of the specified 
 * type upon prompting with the given message. The input is 
 * validated by a callable predicate passed to the function 
 * and will display the given error-message if the predicate 
 * fails.
 * \tparam T The value-type of the input value to gather, 
 *         should be copy-constructible, default-constructible,
 *         and have a valid overload for the stream >> operator.
 * \tparam Predicate A callable predicate type which can 
 *         accept a value of type 'const T&' and return a bool.
 * \param msg The message to prompt the user for input.
 * \param pred The predicate that is called to validate the 
 *             input, should return 'true' if input is valid.
 * \param errMsg The error message displayed if the predicate
 *               fails (or the stream input failed).
 * \return The value obtained from the standard input.
 * \note This function will run indefinitely if the user never
 *       gives a valid input.
 */
template < typename T, typename Predicate >
T takeIn( const std::string& msg, Predicate pred, 
          const std::string& errMsg = "Invalid input!" ) ...

And similarly for other functions.

For the rest, I think it's OK.

Because things, these days, seem a little 'slow' around this forum, please forebare this old post, (that was recently resurrected by an inquiring mind), and humour this 'update', in good faith that this update might be appreciated by some beginning students of C++ ... :)

Hey Mike ... that sure was one super post :)

But I can't help smile a bit more ...

I'm not sure this is really what you should expect a beginner to produce... seems a bit too advanced. Anyways, here are ...

It seems to me ...that a big part of the frustration with some (many?) C/C++ beginners is getting past the 'tricks' of getting valid (especially numeric) input (from a user entering numbers at a keyboard) ?

Thus, I really do appreciate all the code you demo'd.

However, as you noted, things may (quickly) get 'too advanced' for a beginner :)

The goal of designing ...

're-usable generaic code'
vs
'keep it simple' to handle (mainly) 'the job at hand'

...

well ... we can see that ...

it is easy to get carried away :)

So, using the great stuff from your post, here is a suggested first (big) step towards input validation, directed especially at beginners getting (individual data record fields of) meaningful numeric input, without the running program crashing on invalid entries.

Note: this '1st step' does NOT use C++11 stuff like lambda functions or even pre-C++11 functors ...

(Other more advanced suggested steps that use C++ functors may follow ... finally with a C++11 version that uses lambda functions ... etc ... just in case there may be some really keen student here who might like to see all these steps ... that Mike has been so good to have suggested.)

Ok ... a suggested 'first step' that uses a namespace and a separate file to keep a few utility functions handy for your ease of code reuse.

Firstly, a little test program:

// beginnerBadCppExampleCodeFixedUp2.cpp // 2013-08-16 //


#include <iomanip>

#include "takeInUtilities.h"


const int MAX_ARY_SIZE = 2; // keep small while testing/debugging ... //


struct Product
{
    // 4 data members in this data 'record' / data 'struct'
    std::string itemName;
    int numItems;
    double unitPrice; // in local units per item //

    // for internal stat's ...
    char fm; // item was purchased for female or male
    // customer comments about this product (max 160 char's)
    std::string comments;

    double get_total() const { return numItems * unitPrice; }

} ; // <-- note semi-colon to tell compiler struct is 'done' //


void takeIn( Product& prod )
{
    prod.itemName =
        TakeInUtilities::takeInStr( "Item name  : " );

    TakeInUtilities::toAllCaps( prod.itemName );

    prod.numItems =
        TakeInUtilities::takeInNum< int >( "Quantity   : ", 1, 10,
            "\nValid input is a number >= 1 & <= 10 \n" );

    prod.unitPrice =
        TakeInUtilities::takeInNum< double >( "Unit price : ", 0.0,
            1000.00, "\nValid input is a number >= 0.0 & <= 1000.00\n" );
    for( ; ; ) // an example of a C/C++ forever loop
    {
        prod.fm = TakeInUtilities::takeInChr
            ( "Purchase for female/male : " );

        if( prod.fm == 'm' || prod.fm == 'f' || prod.fm == 0 ) break;
        // else ...
        std::cout << "\nOnly f or m or 'blank' valid input here ...\n";
    }

    for( ; ; )
    {
        prod.comments = TakeInUtilities::takeInStr
            ( "Any comments about this product (max char's = 200) : " );

        if( prod.comments.size() <= 200 ) break;
        // else ...
        std::cout << "\nSorry " << prod.comments.size()
                  << " char's won't fit in here ...\n";
    }
}

void showBill( const Product cart[], int size )
{
    using std::setw;
    using std::cout;
    cout << "\nYour cart ...\n"
         << setw( 20 ) << "Item"
         << setw( 15 ) << "Quanity"
         << setw( 15 ) << "Unit Price"
         << setw( 15 ) << "Line Total"
         << '\n';

    double sumTotal = 0.0;

    // show 2 decimal places in money
    cout << std::fixed << std::setprecision( 2 );

    // NOTE! In C/C++ ... arrays start with index 0 ...
    for( int i = 0; i < size; ++ i )
    {
        cout << ' ' << setw( 19 ) << cart[i].itemName
             << ' ' << setw( 14 ) << cart[i].numItems
             << ' ' << setw( 14 ) << cart[i].unitPrice
             << ' ' << setw( 14 ) << cart[i].get_total()
             << '\n';
        sumTotal += cart[i].get_total();
    }

    cout << "\n\nTOTAL: " << sumTotal << " local units of money."
         << "\n\nTHANK YOU FOR YOUR PURCHASE TODAY."
         << "\n\nPLEASE VISIT AGAIN SOON.\n";
}



int main()
{
    Product cart[MAX_ARY_SIZE]; // get room to hold this many products

    using std::cout;
    cout << "\n\t\t\tRaje's Billing Software\n"
         << "\nBelow you will be asked to enter ...\n"
         << "\nITEM NAME,\nQUANTITY,\nUNIT PRICE,\n\nfor each item.\n";

    int no = 0; // inital num products in cart ... //

    do
    {
        cout << "\nFor item " << (no+1) << ", please enter ... \n";

        takeIn( cart[no] );
        cout << '\n';
        ++no;

        if( no == MAX_ARY_SIZE )
        {
            cout << "\nYou have reached the max cart size "
                 << "allowed here of " << no << " order lines.\n"
                 << "Proceeding to 'Check-Out' right now ...\n";
            break;
        }
    }
    while( TakeInUtilities::more() );

    showBill( cart, no );

    TakeInUtilities::takeInStr // allow an empty string as input ... //
    ( "\nPress 'Enter' to continue/exit ... ", false );
}

Now the include file: "takeInUtilities.h"

// takeInUtilities.h // // 2013-08-16 //

#ifndef TAKEINUTILITIES_H
#define TAKEINUTILITIES_H


#include <iostream>
#include <string>
#include <cctype> // re. tolower, toupper
#include <limits> // re. numeric_limits

namespace TakeInUtilities
{
    // use this for your numeric input (only) ... //
    template< typename T >
    T takeInNum
    (
        const std::string& msg,
        const T& min = std::numeric_limits< T >::min(),
        const T& max = std::numeric_limits< T >::max(),
        const std::string& errMsg = "\nInvalid numeric input!\n"
    )
    {
        T val;
        while( true )
        {
            std::cout << msg;
            if( std::cin >> val && std::cin.get() == '\n'
                                && val >= min && val <= max )
                break;
            else
            {
                std::cout << errMsg;
                std::cin.clear();
                std::cin.sync();
            }
        }
        return val;
    }

    // defaults to noEmpty ... but ...
    // allows an empty string as valid input if noEmpty set to false
    std::string takeInStr( const std::string& msg, bool noEmpty = true )
    {
        std::string val;
        for( ; ; )
        {
            std::cout << msg;
            getline( std::cin, val );
            if( noEmpty )
            {
                if( val.size() )
                    break;
                else
                    std::cout << "\nBlank lines are "
                              << "NOT valid here ...\n";
            }
            else
                break;
        }
        return val;
    }


    std::string& toCapsOnAllFirstLetters( std::string& str )
    {
        bool prev_was_space = true;
        int len = str.size();
        for( int i = 0; i < len; ++ i  )
        {
            if( prev_was_space && str[i] != ' ' )
                str[i] = std::toupper ( str[i] );
            prev_was_space = ( str[i] == ' ' );
        }
        return str;
    }

    std::string& toAllCaps( std::string& str )
    {
        int len = str.size();
        for( int i = 0; i < len; ++ i  )
        {
            if( isalpha( str[i] ) )
                str[i] = std::toupper ( str[i] );
        }
        return str;
    }

    // default allows an empty 'takeIn' ...
    char takeInChr( const std::string& msg, bool noEmpty = false )
    {
        std::string reply = takeInStr( msg, noEmpty );
        if( reply.size() == 1 )
            return reply[0];
        // else ...
        return 0;
    }

    // must explicity enter 'n' (or 'N') to have NO more ...
    // interprets empty line input as ... 'yes -> more'
    bool more() // defaults to more ... yes ... more ... //
    {
        if( tolower( takeInChr( "More (y/n) ? " )) == 'n' )
            return false;
        // else ...
        return true;
    }

}

#endif

Here is a version that doesn't need a C++11 compiler to compile ... but uses functors ... so that the programmer can pass in a 'functor' ... a 'function' that here, is used, to help validate the desired input.

A little test program will be followed by a file holding the input 'helper' routines ... in their own namespace.

// test_takeInStuff.h.cpp //

#include "takeIns.h"

// functor def'n needed by following 'takeInString'
struct NoBlankLine
{
    // ctor...
    NoBlankLine( bool noEmpty ) : yes( noEmpty ) {}

    // def'n of overloaded operator () for NoBlankLine
    bool operator() ( const std::string& s ) const
    { return  yes ? s.size() : true; }

private:
    bool yes;
} ;


// will not accept an empty string if noEmpty has default value of true
std::string takeInString( const std::string& msg, bool noEmpty = true )
{
    // firstly, input string using TakeIns::takeIn (with condition):
    std::string result = TakeIns::takeIn < std::string, NoBlankLine >
    (
        msg,
        NoBlankLine( noEmpty ),
        "\nBlank line not valid here!\n"
    );
    return result;
}

// 3 more functor def'ns needed in main ...

struct GreaterThanIntZero
{
    bool operator() ( int i ) const { return i > 0; }
} ;
struct NoNegs
{
    bool operator() ( double i ) const { return i >= 0.0; }
} ;
struct MaleFemale
{
    bool operator() ( char i ) const { return i == 'm' || i == 'f'; }
} ;



int main()
{
    do
    {
        std::string name = takeInString( "Enter your name    : " );
        TakeIns::toCapsOnAllFirstLetters( name );

        std::cout << "\nWith Caps on first letters: " << name << "\n\n";

        int id = TakeIns::takeIn< int, GreaterThanIntZero >
        (
            "Enter your id      : ",
            GreaterThanIntZero(),
            "\nValid here are int size numbers > 0\n"
        );
        std::cout << "\nYou entered " << id << "\n\n";

        double pay = TakeIns::takeIn < double, NoNegs >
        (
            "Enter your payment : ",
            NoNegs(),
            "\nValid here are decimal numbers >= 0.0\n"
        );
        std::cout << "\nYou entered: " << pay << "\n\n";

        char sex = TakeIns::takeIn < char, MaleFemale >
        (
            "Female/Male (f/m)  : ",
            MaleFemale(),
            "\nValid entries are only f or m\n"
        );
        std::cout << "\nYou entered " << sex << "\n\n";

        std::string comments = takeInString
        (
            "Enter any comments : ",
            false
        );
        std::cout << "\nYou entered: \"" << comments << "\"\n\n";

    }
    while( TakeIns::more() );

}

Now the input 'helper' routines ...

// takeIns.h //  // 2013-08-16 //

#ifndef TAKEINS_H
#define TAKEINS_H


#include <iostream>
#include <cctype>

namespace TakeIns
{
    template < typename T >
    bool get_from_cin( T& val )
    {
        std::cin >> val;
        return std::cin && ( std::cin.get() == '\n' );
    }

    inline
    bool get_from_cin( std::string& s )
    {
        getline( std::cin, s );
        return std::cin;
    }

    inline
    bool get_from_cin( char& c )
    {
        std::string s;
        getline( std::cin, s );
        if( s.size() )
            c = s[0];
        else c = 0;
        return std::cin && s.size() <= 1;
    }



    // takeIn (in a loop) until valid ...
    // with a condition to meet before accepting ... //
    template < typename T, typename Predicate >
    T takeIn( const std::string& msg, const Predicate& pred,
              const std::string& errMsg = "\nInvalid input!\n" )
    {
        T result = T();
        while( true )
        {
            std::cout << msg;
            if( get_from_cin( result ) && pred( result ) )
            {
                std::cin.sync();
                break;
            }
            else
            {
                std::cout << errMsg;
                std::cin.clear();
                std::cin.sync();
            }
        }
        return result;
    }

/* if needed ... uncomment and recompile ... was commented out to ...
   show this block was not used by 'this test program' */

/*
    // takeIn (in a loop) until valid type before accepting ... //
    template < typename T >
    T takeIn( const std::string& msg )
    {
        T result = T();
        while( true )
        {
            std::cout << msg;
            if( get_from_cin( result ) )
            {
                std::cin.sync();
                break;
            }
            else
            {
                std::cout << "\nInvalid input!\n";
                std::cin.clear();
                std::cin.sync();
            }
        }
        return result;
    }
*/

    // (no loop) just take in (the first) char entered (on a line) ... //
    char takeInChar( const std::string& msg )
    {
        std::cout << msg;
        char c;
        get_from_cin( c );
        return c;
    }

    // defaults to yes ... do more ... //
    bool more()
    {
        return (  tolower( takeInChar( "More (y/n) ? " ) ) != 'n' );
    }

    std::string& toCapsOnAllFirstLetters( std::string& str )
    {
        bool prev_was_space = true;
        int len = str.size();
        for( int i = 0; i < len; ++ i  )
        {
            if( prev_was_space && str[i] != ' ' )
                str[i] = std::toupper ( str[i] );
            prev_was_space = ( str[i] == ' ' );
        }
        return str;
    }

}

#endif

And here is a version, using Mike's ideas ... but adapting the code to do all the particular condition testing that I choose (for this example) ...

This code needs a C++11 compiler.

It uses lambda functions, etc... (instead of functors as used above) ...

(Just all in one file here ...to ease your testing.)

// takeInStuff_C++11.cpp //

// uses C++11 lambda functions like:  []( int i ) { return i > 0; }
// uses C++11 (for each) function like:  for( char& c : inResultCstr )

#include <iostream>
#include <cctype>


namespace TakeIns
{
    template < typename T >
    bool get_from_cin( T& val )
    {
        std::cin >> val;
        return std::cin && std::cin.get() == '\n';
    }

    inline
    bool get_from_cin( std::string& s )
    {
        getline( std::cin, s );
        return std::cin;
    }

    inline
    bool get_from_cin( char& c )
    {
        std::string s;
        getline( std::cin, s );
        if( s.size() )
            c = s[0];
        else c = 0;
        return std::cin && s.size() <= 1;
    }


    // takeIn (in a loop) until valid ...
    // with a condition to meet before accepting ... //
    template < typename T, typename Predicate >
    T takeIn( const std::string& msg, const Predicate& pred,
              const std::string& errMsg = "\nInvalid input!\n" )
    {
        T result = T();
        while( true )
        {
            std::cout << msg;
            if( get_from_cin(result) && pred(result) )
            {
                std::cin.sync();
                break;
            }
            else
            {
                std::cout << errMsg;
                std::cin.clear();
                std::cin.sync();
            }
        }
        return result;
    }


    // takeIn (in a loop) until valid type before accepting ... //
    // this is needed by 'more' below that calls takeIn< char >( msgStr )
    template < typename T >
    T takeIn( const std::string& msg )
    {
        T result = T();
        while( true )
        {
            std::cout << msg;
            if( get_from_cin( result ) )
            {
                std::cin.sync();
                break;
            }
            else
            {
                std::cout << "Invalid Input!";
                std::cin.clear();
                std::cin.sync();
            }
        }
        return result;
    }


    // will not accept an empty string if noEmpty is 'true' (default)
    std::string takeInString( const std::string& msg, bool noEmpty = true )
    {
        // re-use 'takeIn' (with condition) to get the input string:
        std::string result = takeIn< std::string >
        (
            msg, // prompt msg passed in ... //
            [noEmpty]( const std::string& s ) // lambda function defined here
            {
                return !( noEmpty && ( s.size() == 0 ) );
            }, // end of lambda function def'n ... //

            "\nBlank line not valid here!\n" // errMsg passed in ... //
        );
        return result;
    }

    std::string& toCapsOnAllFirstLetters( std::string& str )
    {
        bool prev_was_space = true;
        int len = str.size();
        for( int i = 0; i < len; ++ i  )
        {
            if( prev_was_space && str[i] != ' ' )
                str[i] = std::toupper ( str[i] );
            prev_was_space = ( str[i] == ' ' );
        }
        return str;
    }

    // defaults to yes ... do more ... //
    bool more()
    {
        return ( std::tolower( takeIn< char >( "More (y/n) ? " ) ) != 'n' );
    }
}




int main()
{
    do
    {
        std::string name = TakeIns::takeInString( "Enter your name : " );
        std::cout << "\nYou entered : " << name;

        TakeIns::toCapsOnAllFirstLetters( name );
        std::cout << "\nCaps ...    : " << name << "\n\n";

        int id = TakeIns::takeIn< int >
        (
            "Enter your id   : ",
            []( int i ) { return i > 0; },
            "\nValid here are int size numbers > 0\n"
        );
        std::cout << "\nYou entered " << id << "\n\n";

        double pay = TakeIns::takeIn< double >
        (
            "Enter your payment : ",
            []( double i ) { return i >= 0; },
            "\nValid here are decimal numbers >= 0.0\n"
        );
        std::cout << "\nYou entered " << pay << "\n\n";

        char sex = TakeIns::takeIn< char >
        (
            "Male/Female (f/m)  : ",
            []( char i ) { return i == 'm' || i == 'f'; },
            "\nValid entries are only f or m\n"
        );
        std::cout << "\nYou entered " << sex << "\n\n";

        std::string comments = TakeIns::takeIn< std::string >
        (
            "Enter any comments : "
        );
        std::cout << "\nYou entered \"" << comments << "\"\n\n";

    }
    while( TakeIns::more() );

}
Member Avatar for iamthwee

@the OP, consider investing your time in more useful endeavours.

Most of this is useless, for entry level programmers and completely convoluted. For that reason it is NOT a better example.

If you want a simple and fool proof way for input handling consider taking everything in as strings and running your own checking functions on them for validity.

Consider reading the following article.

http://www.daniweb.com/software-development/cpp/threads/90228/flushing-the-input-stream

See my comment in post #2.

Brevity ALWAYS wins in my opinion.

If you want a simple and fool proof way for input handling consider taking everything in as strings and running your own checking functions on them for validity.

For standard types provided by the language, end user programmers shouldn't be expected to validate or parse them unless they want. However, simple and fool proof are mutually exclusive in the case of stream I/O. ;) Case in point, the OP's TakeIns are incomplete and the huge variance in I/O needs would find a programmer either extending the class...well, extensively, or dumping it entirely in favor of more common ad hoc approaches.

If I were to write something like this, the back end would almost certainly be based on string input and customized validation that can scale. But I'd also include a caching mechanism that minimizes the single biggst issue with stream I/O and robustness: lookahead.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.