LinkDude80
LinkDude80

Reputation: 31

Parsing command line char arguments as ints with error checking in C++

I'm trying to write a program that takes in two ints as command line arguments. The ints both need to be greater than 0. I understand that I need to convert from char but I have only ever done that using atoi which I now know that I shouldn't do. I've seen people use sstreams and strtol but I'm not sure how those would work in this case. What is the best way to accomplish this?

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

using namespace std;

const int N = 7;
const int M = 8;//N is number of lines, M number of values

//--------------
//-----Main-----
//--------------
int main(int argc, char* argv[])
{
    if((argc != 0) && (argv[0] != NULL) && (argv[1] != NULL))
    {       
        N = argv[0];
        M = argv[1];
    }
    else
    {
        cout << "Invalid or no command line arguments found. Defaulting to N=7 M=8.\n\n" << endl;
    }


    //Blah blah blah code here

    return 0;
}

Upvotes: 2

Views: 4021

Answers (3)

Cubic
Cubic

Reputation: 15683

In C++11 there's stoi, stol, stoll for this: http://en.cppreference.com/w/cpp/string/basic_string/stol

Those throw invalid_argument or out_of_range exceptions if the string isn't in the right format.

There's nothing particularly wrong about using atoi, except it doesn't have a mechanism to report exceptions because it's a C function. So you only have the return value - the problem is all return values of atoi are valid values, so there's no way to differentiate between the return value of 0 as the correct parsing of "0" or the failure of parsing. Also, atoi doesn't do any checks for whether the value is outside the available value range. The first problem is easy to fix by doing the check yourself, the second is more difficult because it involves actually parsing the string - which kind of defeats the point of using an external function in the first place.

You can use istringstream like this:

Pre-C++11:

int val;
std::istringstream iss(arg[i]);
iss >> val;
if (iss.fail()) {
   //something went wrong
} else {
    //apparently it worked
}

C++11:

int val;
std::istringstream iss(arg[i]);
iss >> val;
if(iss.fail()) {
   if(!value) {
       //wrong number format
   } else if(value == std::numeric_limits<int>::max() || 
             value == std::numeric_limits<int>::min() 
   {
       //number too large or too small
   }
} else {
   //apparently it worked
}

The difference is that pre C++11, only format errors were detected (according to standard), also it wouldn't overwrite the value on error. In C++11, values are overwritten by either 0 if it's a format error or max/min if the number is too large or too small to fit into the type. Both set the fail flag on the stream to indicate errors.

Upvotes: 1

Nemanja Boric
Nemanja Boric

Reputation: 22157

First, you can't use const qualifier for M and N, since you will change their value:

int N = 7;
int M = 8;//N is number of lines, M number of values

Second, you don't need to check for (argv[0] != NULL) && (argv[1] != NULL), just check if argc (argument count) is greater or equal to 3:

if(argc >= 3)

Then you need to convert this into integers. If you don't want to use atoi, and if you don't have C++11 compiler you should use C++'s stringstream or C's strtol

stringstream ss;
int temp;
ss << argv[1]; // Put string into stringstream
ss >> temp;    // Get integer from stringstream
// Check for the error:
if(!ss.fail())
{
    M = temp;
}

// Repeat 
ss.clear(); // Clear the current content!
ss << argv[2]; // Put string into stringstream
ss >> temp;    // Get integer from stringstream
// Check for the error:
if(!ss.fail())
{
    N = temp;
}

so, whole code will look like this:

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sstream>
using namespace std;

int N = 7;
int M = 8;//N is number of lines, M number of values

//--------------
//-----Main-----
//--------------
int main(int argc, char* argv[])
{
    if(argc >= 3)
    {       
       stringstream ss;
       int temp;
       ss << argv[1]; // Put char into stringstream
       ss >> temp;    // Get integer from stringstream
       // Check for the error:
       if(!ss.fail())
       {
           M = temp;
       }

       // Repeat
       // empty
       ss.clear();
       ss << argv[2]; // Put char into stringstream
       ss >> temp;    // Get integer from stringstream
       // Check for the error:
       if(!ss.fail())
       {
           N = temp;
       }

    cout << M << " " << N;
    }
    else
    {
        cout << "Invalid or no command line arguments found. Defaulting to N=7 M=8.\n\n" << 

endl;
    }


    //Blah blah blah code here

    return 0;
}

Also, C header files include with c prefix, not with .h suffix (<cstdio> instead of <stdio.h>)

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490148

In this specific case, atoi will work fine. The problem with atoi is that you can't differentiate between its returning 0 to signify an error of some sort, and its returning 0 to indicate that the input was 0.

In your case, however, a valid input must be greater than 0. You don't care whether the input was 0 or something else that couldn't be converted. Either way you're doing to set it to the default value.

As such, I'd do something like:

int convert(char *input, int default) { 
    int x = atoi(input);
    return x==0 ? default : x;
}

if (argc > 1)
    N = convert(argv[1], 7);

if (argc > 2)
    M = convert(argv[2], 8);

Note that argv[0] traditionally holds the name of the program being invoked. Arguments passed on the command line are received as argv[1] through argv[argc-1].

Upvotes: 0

Related Questions