Input arguments with C++

I am using getopt_long to pass arguments to a C++ program. Arguments are passed with a key and a value separated by a space of '=' sign. Some options do not need to assign a value to them.

Example:

./raytrac dist=0.3 --color

I have some classes that need to use some of these parameters ant created a class Parsing to handle the arguments passed from argv.

   bool  Parsing::get_int
   bool  Parsing::get_float
   bool  Parsing::get_vect2
   bool  Parsing::get_string

Then if a class (let us say Tomog) needs those parameters I call

Tomog::set_param(Pc)

Where Pc is of class Parsing. Pc is a list associating a key with the corresponding argument. If the key matches you get the value.

Example

    float f;
    if (Pc.get_string("--dist",f)) {

Hence if the user passed --dist=0.3, f will contain the value 0.3

I am wondering what people think about this system of passing command line variables. And suggestions if there are better ways to do this.

Some more details of Tomog::set_param is given below

void Tomog::set_param (
  Parsing&  Pc
) {

  int  i;
  int  nf;

  String  s;
  String  T;

    if (Pc.get_string("--lglvl",s)) {
    if ( !get_log_level(s, lglvl) ) {
      error("invalid value for --lglvl");
    }
    cerr << "Tomog::set_param, --lglvl = " << s << endl;
  } else {
    lglvl = normal;
    cerr << "Tomog::set_param, --lglvl = normal = " << lglvl << endl;
  }
float f;
 if (Pc.get_float("--dist", f)) {
    nf = s.nfields(':');
    if (nf < NLayers) {
      error ("value for --nzs is less than the number of layers");
    }
    cerr << "Tomog::SetParam, nzs = " << s << endl;
    for (i = 0; i < NLayers; i++) {
      T = s.get_token(':');
      T >> NzS;
    }
  } else {
    NzS.fill(2);
  }

  ifstream  ifs_base;
  Parsing  Pb;
  if ( !Pc.get_string("--ifbase", s) ) {
    error ("no value for --ifbase");
  }
  ifs_base.open(s);
  if (ifs_base.bad()) {
    error ("ifs_base failed to open");
  }
  Pb.parse_file(ifs_base);
  if (lglvl >= medium) {
    cerr << "Tomog::set_param, --ifbase = " << s << endl;
  }

  // get model boundaries from base file

  Vect2  Xi;
  Vect2  Xf;
  Pb.get_vect2("XI", Xi);
  Pb.get_vect2("XF", Xf);

  cerr << "Tomog::set_param: Xi = " << Xi << endl;
  cerr << "Tomog::set_param: Xf = " << Xf << endl;

// Read Travel Time Data File --------------------------------------------------

  Parsing  Pd;

  // tdat ----------------------------------------------------------------------
  // name of file containing travel times

  ifstream  ifs_tdat;
  if ( !Pc.get_string("--iftdat", s) ) {
    error ("no value for --iftdat");
  }
  ifs_tdat.open (s);
  if (ifs_tdat.bad()) {
    error ("ifs_tdat failed to open");
  }
  cerr << "Tomog::SetParam, ifs_tdat = " << s << endl;
  Pd.parse_file (ifs_tdat);

  Mod.set_data(Pd);
  Mod.set_param(Pc);    // calling ModMisfit::SetRealData

This scheme seems a bit messy, since it lets you splatter commandline argument parsing wherever you feel like, instead of keeping it central in main(). And it's not really more organized than the scheme you had before, which was just a big list of variables.

What is a vect2? A 2d vector, but of what, ints, floats, doubles?

Yes, vect2 is a class I created. It is a template so can handle various types. It has some functions for cartesian vector computations.

I have discussed this with another person who said the same thing. Thus I am planning to recode this part of the program. I would need suggestions on how to go about a solution.

I wrote some generic commandline parsing code years ago because I wasn't happy with how pointlessly complicated C getopt was, and have been using it in nearly all my projects ever since.

The idea is, you give the program an array of arguments to look for and pointers to variables to fill. Then you plug argc and argv into tsm_getopt, and it fills them if able.

Here is how it looks:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "tsm_getopt.h"

// Structure to keep all global variables in one place.
struct {
        float fval;
        const char *stringval;
        const char *mandatorystring;
        int ival;
        int optionalflag;
        float mandatoryfloat;
} globals={
        3.14159,                // Default value for fval
        "where in the world",   // Default value for stringval
        NULL,                   // Mandatory, NULL if not set for error
        0,                      // Default value for ival
        0,                      // default value for optionalflag
        0.0/0.0,              // mandatory, defaults to invalid 0/0 if not set
};

const opt options[]={
        // "fval" for "--fval" argument,
        // 'f' for '-f' argument,
        // 1 since it takes one argument,
        // a pointer to a float number,
        // "%f" to read one float,
        // and "Float Value" to print as help text.
        {"fval", 'f', 1, &globals.fval, "%f", "Float value"},
        // Similar, but an integer number.
        {"ival", 'i', 1, &globals.ival, "%d", "Integer value"},
        // A string.  Needs a pointer to a char *(not the char * itself).
        {"sval", 's', 1, &globals.stringval, NULL, "String Value"},
        {"mstr", 'm', 1, &globals.mandatorystring, NULL, "Mandatory string"},
        // An option with no parameters.
        // Changes globals.optionalflag from 0 to 1 if set.
        {"optionalflag", 'o', 0, &globals.optionalflag, NULL, "optional flag"},
        // Need this as the last thing in the list, to tell when the list ends
        {"mfloat", 'l', 1, &globals.mandatoryfloat, "%f", "Mandatory float"},
        {NULL},
};

int main(int argc, char *argv[])
{
        if(argc < 2) // No arguments
        {
                tsm_usage(argc, argv, options);
                exit(1);
        }

        if(tsm_getopt(argc, argv, options) < 0)
        {
                tsm_usage(argc, argv, options);
                exit(1);
        }

        // Check any mandatory arguments
        if(globals.mandatorystring == NULL)
        {
                printf("mandatorystring not set\n");
                exit(1);
        }

        if(globals.ival == 0)
        {
                printf("ival not set\n");
                exit(1);
        }

        if(isnan(globals.mandatoryfloat))
        {
                printf("mfloat not set\n");
                exit(1);
        }

        // Arguments should now be ready.
        printf("fval = %f\n", globals.fval);
        printf("stringval = %s\n", globals.stringval);
        printf("mandatorystring = %s\n", globals.mandatorystring);
        printf("ival = %d\n", globals.ival);
        printf("optionalflag = %d\n", globals.optionalflag);
        printf("mandatoryfloat = %f\n", globals.mandatoryfloat);
        return(0);
}

It uses this code:

/* tsm_getopt.c */
#include <stdio.h>
#include <string.h>
#include "tsm_getopt.h"

static int tsm_findarg(const char *str, const opt options[]);

static int tsm_findarg(const char *str, const opt options[])
{
        int n;
        if(str[1] == '\0')
                return(-1);

        for(n=0; options[n].lname != NULL; n++)
        {
                // "-s"
                if((options[n].sname) &&
                        (str[1] == options[n].sname) &&
                        (str[2] == '\0'))
                        return(n);

                // "-s=..."
                if((options[n].sname) &&
                        (str[1] == options[n].sname) &&
                        (str[2] == '='))
                        return(n);

                // "--sval" or "--sval=..."
                if(str[1] == '-')
                if(strncmp(str+2, options[n].lname, strlen(options[n].lname))==0)
                if((str[2+strlen(options[n].lname)]=='\0')||
                   (str[2+strlen(options[n].lname)]=='='))
                        return(n);
        }

        return(-1);
}

int tsm_usage(int argc, char *argv[], const opt options[])
{
        int n;

        fprintf(stderr, "Usage:  %s\n", argv[0]);

        for(n=0; options[n].lname != NULL; n++)
        {
                fprintf(stderr, "\t--%s", options[n].lname);
                if(options[n].sname)
                        fprintf(stderr,"(-%c)", options[n].sname);
                if(options[n].usage)
                        fprintf(stderr, "\t%s", options[n].usage);
                fprintf(stderr, "\n");
        }
}

int tsm_getopt(int argc, char *argv[], const opt options[])
{
        int arg=1;
        while(arg < argc)
        {
                int which;
                if(strcmp(argv[arg], "--") == 0)
                        return(arg+2);
                else if(argv[arg][0] == '-')
                {
                        which=tsm_findarg(argv[arg], options);
                        if(which < 0)
                        {
                                fprintf(stderr, "invalid option '%s'\n",
                                        argv[arg]);
                                return(-1);
                        }

                        if(options[which].args)
                        {
                                // Handle -s=asdf or --sval=asdf
                                char *eq=strchr(argv[arg], '=');
                                char *argstr=NULL;

                                if(eq != NULL)
                                {
                                        eq++;
                                        if(eq[0] == '\0') eq=NULL;
                                }

                                if(((argc - arg) < 2)&&(eq==NULL))
                                {
                                        fprintf(stderr, "Need argument for %s\n",
                                                argv[arg]);
                                        return(-1);
                                }

                                if(eq) argstr=eq;
                                else    argstr=argv[arg+1];

                                // Store if possible
                                if(options[which].val)
                                {
                                        if(options[which].cmdstr)
                                        {
                                                if(sscanf(argstr,
                                                        options[which].cmdstr,
                                                        options[which].val) != 1)
                                                {
                                                        fprintf(stderr, "Invalid value "
                                                                "'%s' for option '%s'\n",
                                                                argv[arg], argv[arg+1]);
                                                        return(-1);
                                                }
                                        }
                                        else
                                                *((char **)options[which].val)=argstr;

                                        if(eq == NULL)  arg++;
                                }
                        }
                        else if(options[which].val != NULL)
                        {
                                // flag if possible
                                *((int *)options[which].val)=1;
                        }

                        arg++;
                }
                else
                        return(arg);
        }

        return(arg);
}
/* tsm_getopt.h */
#ifndef __TSM_GETOPT_H__
#define __TSM_GETOPT_H__

typedef struct opt
{
        const char      *lname;         // Long option, mandatory.
        unsigned char   sname;          // Short option, optional.
        int             args;           // Has arguments.
                                                // When cmdstr is NULL, stores
                                                // pointer to arg in *val
                                                // when cmdstr isn't NULL,
                                                // scanf's string into val
        void            *val;           // What to store
        const char      *cmdstr;        // Command string for scanf
        const char      *usage;         // Usage description
} opt;

#ifdef __cplusplus
extern "C" {
#endif

// Parse commandline parameters.  Returns index of first unparsed value.
int tsm_getopt(int argc, char *argv[], const opt options[]);
// Print usage for optional parameters
int tsm_usage(int argc, char *argv[], const opt options[]);

#ifdef __cplusplus
}
#endif

#endif/*__TSM_GETOPT_H__*/

I've just modified it to accept both --arg string and --arg=string syntax.

I'd need to see your vect2 class to add it to this, but maybe you could add the ability to be initialized from strings to your vect2 class instead?

I am going to stick with the getopt_long for now. My only question is how can I have the class Tomog set the value for dist as example.

Since these are supposed to be global, putting them into a class doesn't make much sense, how about a namespace instead?

namespace options {
        int x, y, z;

        bool parse(int argc, char *argv[])
        {
                x=5;
                y=6;
                z=7;
        }
};

int main(int argc, char *argv[])
{
        if(!options::parse(argc, argv))
        {
                return(1);
        }

        printf("Got x %d\n", options::x);
        printf("Got y %d\n", options::y);
        printf("Got z %d\n", options::z);
        return(0);
}

That's with it all in one file. If you wanted the namespace accessible from multiple different C++ files, it'd be like this:

#ifndef __NS_H__
#define __NS_H__

namespace options {
        extern int x, y, z;
        bool parse(int argc, char *argv[]);
};

#endif/*__NS_H__*/
#include <stdio.h>
#include "ns.h"

int main(int argc, char *argv[])
{
        if(!options::parse(argc, argv))
        {
                return(1);
        }

        printf("Got x %d\n", options::x);
        printf("Got y %d\n", options::y);
        printf("Got z %d\n", options::z);
        return(0);
}

// These definitions only belong in one single file
int options::x=-1, options::y=-1, options::z=-1;

bool options::parse(int argc, char *argv[])
{
        x=5;
        y=6;
        z=7;
}

My problem is not the parsing routine itself that takes the arguments and parses them in main, but how to get a class Tomog set the value for dist which was supplied by the parser in main.

That's what the namespace is for, it stores the data outside of main(). Once you do #include "ns.h" like in my code, you can use options::x wherever you need it, including class constructors.

Be sure to declare variables inside ns.h as extern, then declare them properly in one and only one file like I've shown you in my last post. The same goes for functions -- don't define their internals in the .h file. When you do it this way, you'll get the same variables available in all files across your program.

Basically in my program I have main() that uses getopt_long to process the values passed by the user.

Suppose in main() the user can pass the following arguments

      {"interp-p",      required_argument, 0,  6},
       {"varp",      required_argument, 0,  7},
     {"ifbase",  required_argument, 0,  8},
     {"iftdat",  required_argument, 0,  9},
     {"ifmodl",  required_argument, 0,  10},
      {"ifpopl",  required_argument, 0,  11},
      {"ofmodl",  required_argument, 0,  12},
      {"ofpopl",  required_argument, 0,  13},
      {"ofexpl",  required_argument, 0,  14},
      {"nlayers", required_argument, 0,  15},
      {"ni",      required_argument, 0,  16},
      {"nxp",     required_argument, 0,  17},
      {"nzp",     required_argument, 0,  18},
      {"nxs",     optional_argument, 0,  19},
      {"nzs",     optional_argument, 0,  20},
      {"ngenr",   optional_argument, 0,  21},
      {"nmodl",   optional_argument, 0,  22},
      {"objv",    optional_argument, 0,  23},
      {"genitor", optional_argument, 0,  24},
      {"chrmrep", optional_argument, 0,  25},
      {"elitsel", optional_argument, 0,  27},
      {"msel",    optional_argument, 0,  26},
      {"mcrs",    optional_argument, 0,  28},
      {"rsel",    required_argument, 0,  29},
      {"rcrs",    optional_argument, 0,  30},
      {"rmut",    optional_argument, 0,  31},
      {"hdwvs",   optional_argument, 0,  32},
      {"tstep",   optional_argument, 0,  33},
      {"intgm",   optional_argument, 0,  34},
      {"dangl",   optional_argument, 0,  35},
      {"textr",   optional_argument, 0,  36},
      {"sigma",   optional_argument, 0,  37},
      {"colour",        no_argument, 0,  33},
      {"lglvl",   optional_argument, 0,  34},
      {"brief",         no_argument, 0, 'b'},
      {"detailed",      no_argument, 0, 'd'},
      {"examples",      no_argument, 0, 'e'},
      {"help",          no_argument, 0, 'h'},

Then I define an object T as follows

Tomog  T;

Somehow I need to have object T know about the values for

--ifbase
--iftdat
--nxp
--nzp
--varp
--interp-p

So I need to come up with a good scheme to do this

I repeat. That is what the namespace is for. You can use them from anywhere at all.

It's like global variables, in that there's only one of them that you can access from anywhere -- you do not need to pass around arrays or arguments to get your data. But it's declared like a class and can contain functions, so you can keep it nicely organized just like a class.

You see me use them in main, since that's where argv[] comes from. Nothing stops anything else from reading it.

class myclass
{
public:
        // options::x was set by config::parse(), called by main().
        // We can use it directly, no need to pass anything anywhere.
        myclass() { x = options::x; }
        int x;
};
int main(int argc, char *argv[])
{
       options::parse(argc, argv);
       myclass something;
}

// These lines should only be in one file, but the functions
// and variables they instantiate can be used globally.
int options::x=-1, options::y=-1, options::z=-1;

bool options::parse(int argc, char *argv[])
{
        x=5;
        y=6;
        z=7;
}

Does this mean that I can set x, y and z from argv?

// These lines should only be in one file, but the functions
// and variables they instantiate can be used globally.
int options::x=-1, options::y=-1, options::z=-1;

bool options::parse(int argc, char *argv[])
{
        x = argv[0];
        y = argv[1];
        z = argv[2];
}

Yes. Now you're getting it. :slight_smile:

And I suppose I can use getopt_long in

option::parse

Yes. Anything you can do in main() you can do in option::parse\(\)

1 Like

Ok, so this is one method.

I also outline another method

int main(int argc, char *argv[])
{

  Tomog T;
  T.set_parameter("dist",dist);
  T.set_parameter("ifmodl",ifmodl);
  T.set_parameters(dist,ifmodl,...etc);
}

or pass a structure if I have a lot of parameters

int main(int argc, char *argv[])
{

  Tomog T;
  T.set_parameter("dist",dist);
  T.set_parameter("ifmodl",ifmodl);
  T.set_parameters(config_parameters); 
}

How does this method compare with what you propose and what would ideally be your suggestion?

The namespace is the exact same thing done a simpler way, avoiding some pitfalls that could happen when using Tomog.

It's possible for there to be more than one Tomog object, which can cause confusion and bugs. You could make bad, shallow copies of Tomog that crash later if you accidentally pass by value instead of reference. You might accidentally use the wrong Tomog, or a new/blank Tomog, instead of the one you set up in main(). You might try and change a value in one Tomog and not have it show up later because you changed a copy, not the original. You might end up with multiple Tomog's slightly different from each other.

If you use your Tomog class exactly perfectly correctly, keeping one and only one instance which you pass by reference everywhere, that's exactly what a namespace acts like anyway.

I understand what you mean. I only have a single Tomog used in Main. With the discussion here, both methods are better than how I was doing things, passing everything.

Do you see the use of namespaces for passing variables to classes in this way in actual applications?

Passing by-value instead of by-reference will make duplicates, potentially leading to one or more of the problems mentioned above. All it takes is a single missed &. But you can't duplicate a namespace even by accident.

Yes. The standard cin, cout, and cerr variables exist inside the std namespace, for example.

1 Like

Brilliant. I am upgrading this old code written by someone else and am grateful for the suggestions to redo some thing better. :b: It's my first big project in C++. Before, they were mainly toy problems.