1

Here's a C++ function of mine:

void SetUserName(char username[])
{
    cout << "\nPlease enter a username.\n"
         << "Must not be longer than 12 characters.\n>> ";

    cin.getline(username, MAX) // MAX is globally defined

    while(strlen(username) > MAX)
    {
        cout << "\nUsername too long, try again.\n>> ";
        cin.getline(username, MAX);
    }
}

Obviously, the while loop never works because the user input is truncated to 12 characters everytime.

How can I effectively determine if the user input was too long, and continue to loop until the conditions are met?

Edit: Using cstring here is a requirement. I already know how easy it is with strings.

Edit #2: This was definitely a fruitful question for me, as it taught me a lot. Final code: http://pastie.org/3537894

skippr
  • 2,656
  • 3
  • 24
  • 39
  • 1
    Why is using `cstring` a requirement? If that's the case, you should be programming in C and not C++. – Jesse Good Mar 06 '12 at 23:15
  • @Jesse Whether I'm programming in C or C++, I still want to know if there is a way to achieve the result I'm looking for with cstrings. The PL is probably not important, but I state it anyways. – skippr Mar 06 '12 at 23:18
  • 1
    Is this a "C++" assignment where you are not allowed to use `std::string`? – Kurt Mar 06 '12 at 23:20
  • @Prowla it's related to one. However, the assignment didn't ask for any response to input that was too long. I didn't feel right truncating without providing some response to the user, and I wasn't able to figure out a cstring solution. – skippr Mar 06 '12 at 23:22
  • @sunday no worries. In that case I would do as dasblinkenlight suggested. Just be careful, I have came across some ignorant markers; Some respect initiative and some don't. – Kurt Mar 06 '12 at 23:39
  • 1
    @Prowla Thanks for the advice; I'm doing this outside of the assignment though (it's already been turned in and graded), so this is merely for self-learning purposes. – skippr Mar 06 '12 at 23:42

4 Answers4

4

C-style terminated strings are rather tricky to work with, and in almost every case I'd recommend the C++ std::string instead. However, since you say you specifically want to read a terminated string into an array, here is one way to do it.

Remember that the array size must be MAX+1, so there's space for MAX characters followed by the terminator.

istream::getline sets the streams failbit flag if the line is too long; you can test this after reading. If only a partial line was extracted, and you want to move on to the next line, then you'll need to clear the error state, and ignore the rest of the line.

while (!std::cin.getline(buffer, MAX+1)) {
    std::cout << "Too long\n";
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • I implemented your solution. See it here: http://pastie.org/3537767 Is it possible to get rid of the boolean flag I have and use only a while loop with no if/else? If so, how? Putting the getline statement within the while loop conditional didn't seem to work. Also, can you explain to me what the last two lines of the ELSE section are doing? (the clear and ignore statements) ... feel free to make your own pastie, or edit your post here. – skippr Mar 07 '12 at 00:58
  • @sunday: you can get rid of the flag by writing the loop as `while (!getline()) {clear(); ignore(); complain();}`. The `clear()` clears the sticky error flag; without that, all further attempts to read would fail. The `ignore()` reads and ignores everything up to and including the next newline character; without that, the next `getline()` would read the remainder of the long line, rather than the next line. – Mike Seymour Mar 07 '12 at 01:10
  • @sunday: I've changed my example code to better match your retry loop. – Mike Seymour Mar 07 '12 at 01:13
  • @Mike_Seymour What is the 'numeric_limits::max()' and '\n' within ignore() for? Do I NEED them? Those were what threw me off really. – skippr Mar 07 '12 at 01:14
  • In other words, will a simple cin.ignore() - no arguments, work any differently? – skippr Mar 07 '12 at 01:17
  • @sunday: `numeric_limits::max()` is the maximum value of the `streamsize` type - it means there's no limit on the number of characters to ignore. `\n` is the newline character - it means read until the end of the line. You do need both of them; without the first it would only ignore one character, and without the second it would ignore all the rest of the stream. – Mike Seymour Mar 07 '12 at 01:17
3

Use proper C++ (in particular, strings and the free getline function):

#include <string>
#include <iostream>

std::string line;
while (std::getline(std::cin, line))
{
    if (line.length() > 12)
    {
        // error, line too long

        continue;
    }

    // process line
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • @muntoo: Of course. The loop condition is false when the input stream is closed or exhausted. – Kerrek SB Mar 06 '12 at 23:14
  • Because you received 2 more upvotes after the edit was placed. People obviously aren't reading the question. – skippr Mar 06 '12 at 23:36
  • @sehe his answer does not solve my problem. Granted this was my fault, I have provided an edit clarifying the conditions of the answer. – skippr Mar 06 '12 at 23:54
3

If you want to find out if std::istream::getline() read an array full of characters as demanded but not an end of line character you need to figure out whether the number of stored characters (minus the terminating null) is identical to the extracted characters. That is, the following determines if there are more then 12 characters on the line (the 13th character is needed for the terminating null):

#include <iostream>
#include <string.h>

int main()
{
    char array[13];
    if (std::cin.getline(array, 13).gcount() == strlen(array)) {
        std::cout << "excess characters on the line\n";
    }
}

If you next also want to remove the excess characters from the stream you'd use something like std::cin.ignore(std::numeric_limits<std::streamsize>::max());. Since this is tagged as C, too, I don't know off-hand how to do this with C but I'm pretty sure that there is something similar to gcount().

Actually, looking more closely at the spec std::istream:getline() actually sets std::ios_base::failbit if it doesn't encounter a newline while reading the character (it also sets std::ios_base:failbit when no character is read but it doesn't set std::ios_base::failbit if at least one character is read before end of file is reached). This mean, you also want to clear the stream before ignoring excess characters and you can work off std::ios_base::failbit and std::ios_base::eof():

#include <iostream>
#include <limits>
#include <string.h>

int main()
{
    char array[13];
    std::cout << "enter password: ";
    while (!std::cin.getline(array, 13) && !std::cin.eof())
    {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "password is too long: enter max 12 chars: ";
    }
    std::cout << "the password is '" << array << "'\n";
}

Since std::ios_base::failbit is set you need to call clear() before you can use the stream for anything.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I'm a bit confused about how to use this method within a loop to continue requesting the user to input a username with a valid length. Can you provide further insight on that? – skippr Mar 07 '12 at 00:27
  • Well now you've changed your post to almost exactly what the [now selected] answer has. Nice. – skippr Mar 07 '12 at 18:48
  • I responded to your request and showed an approach you can use. When testing the code I noticed that `failbit` gets set and needs to be cleared. When determining the stored length vs. the consumed characters I didn't remember the details about `failbit`. Note that my code copes with premature EOF while the solution get an infinite loop. – Dietmar Kühl Mar 07 '12 at 18:58
1

For the user to enter more characters than you allow, he must go at least one character over the limit. Since it does not matter to you by how many characters the user has "overstepped" your limit, you can pass MAX+1 as your limit, and see if the length is greater than MAX.

Of course you need to make enough space in the buffer to hold the 13-th character and a zero terminator. EDIT You also need to call ignore to skip to the end of the line on each failed attempt.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Can you provide sample code of how to implement this? You are not the only one to propose this solution (my own prof is saying it), but I have yet to see anyone actually make it work without some ignore() statements or other such calls. – skippr Mar 07 '12 at 18:44
  • @sunday This is a very good point! Since `getline` stops as soon as it reaches the limit that you passed in, you do need to call `ignore` to skip to the end of the buffer after each failed attempt. – Sergey Kalinichenko Mar 07 '12 at 19:17