Suppose I have a main function which basically just calls one other function as entry point to the program. The function (and thus the full program) has a number of mandatory and a number of optional parameters:
#include <iostream>
#include <sstream>
void function_to_call(std::string arg1,
std::string arg2,
std::string arg3,
std::string arg4,
std::string arg5 = "foo",
std::string arg6 = "bar",
int num1 = 1,
int num2 = 2
)
{
// do fancy stuff here
}
int main(int argc, char** argv)
{
int num1, num2;
std::stringstream stream;
if( argc < 5 ) {
std::cerr << "Usage: \n\t" << argv[0]
<< "\n\t\t1st argument"
<< "\n\t\t2nd argument"
<< "\n\t\t3rd argument"
<< "\n\t\t4th argument"
<< "\n\t\t5th argument (optional)"
<< "\n\t\t6th argument (optional)"
<< "\n\t\t7th argument (optional)"
<< "\n\t\t8th argument (optional)"
<< "\n\t\t9th argument (optional)" << std::endl;
}
if( argc == 5 ) {
function_to_call( argv[1], argv[2], argv[3], argv[4] );
}
if( argc == 6 ) {
function_to_call( argv[1], argv[2], argv[3], argv[4], argv[5] );
}
if( argc == 7 ) {
function_to_call( argv[1], argv[2], argv[3], argv[4], argv[5], argv[6] );
}
if( argc == 8 ) {
stream << argv[7];
stream >> num1;
function_to_call( argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], num1 );
}
if( argc == 9 ) {
stream << argv[7] << ' ' << argv[8];
stream >> num1 >> num2;
function_to_call( argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], num1, num2 );
}
return 0;
}
The if
chain could maybe be replaced with a switch
, the command line might be tidied up a bit by using the getopt
library or boost program_options
, but that doesn't really change things conceptually.
Is there an obvious way I am missing to handle different numbers of parameters?
I realize one way of doing this might be to use vector of a (trivial) base class and then have small specializations for the different types e.g. like this (not necessarily optimal):
class Base
{
public:
virtual void set(const char *) = 0;
};
class Int : public Base {
public:
Int(int value) : value_(value) {}
Int(const char* value) : value_(std::stoi(value)) {}
virtual void set(const char* value) { value_ = std::stoi(value); }
int get() { return value_; }
private:
int value_;
};
class Str : public Base {
public:
Str(const char* value): value_(value) {}
virtual void set(const char* value) { value_ = value; }
std::string get() { return value_; }
private:
std::string value_;
};
Then the option parsing could be done like this, i.e. have the compiler figure out which type we are dealing with
int main(int argc, char** argv)
{
std::vector<Base*> theopts = { new Str(""),new Str(""),new Str(""),new Str(""),new Str("foo"),new Str("bar"),new Int(1),new Int(2) };
if( argc < 5 ) {
// put meaningful handling here
}
for( int i = 0; i < argc-1; ++i ) {
theopts[i]->set(argv[i+1]);
}
function_to_call( static_cast<Str*>(theopts[0])->get(),
static_cast<Str*>(theopts[1])->get(),
static_cast<Str*>(theopts[2])->get(),
static_cast<Str*>(theopts[3])->get(),
static_cast<Str*>(theopts[4])->get(),
static_cast<Str*>(theopts[5])->get(),
static_cast<Int*>(theopts[6])->get(),
static_cast<Int*>(theopts[7])->get()
);
}
The function call then obviously is a bit ugly due to the explicit casting, but the number of explicit if
s is very small in this implementation.