2
votes

I've been writing a command line arguments parser for an application. These can be of three types: boolean, string, or numeric. A given argument will expect a given type, so there is no guesswork here.

I chose to represent an argument as a class, called Arg. The same switch can appear multiple times, so I wish keep track of identities, hence the class.

The thing is, although there are multiple types at play, the processing is always more or less the same. In order to avoid writing code, I thought I could template the Arg class, essentially like this:

class Arg(T)
{
    public:
        T value;

        this(T defaultValue)
        {
            value=defaultValue;
        }
}

My problem is that I would ideally just stack those in a dynamic array, but I would have to know the type of them first : I can't exactly declare an array as Arg!whatever[] myArray.

I can obviously use a base type, something like Object[] array. The thing is, I'd then have to cast each array item before using it. I could write a helper function for this, but it hardly seems idiomatic. I would, however, keep all the type-related pain in a single function. (this is what I'd do without external advice)

I suppose I could also maintain separate arrays. But this isn't too convenient: I need to think about the various numeric types, and if I were to add new argument types, I'd have to add new arrays.

I also tried using std.variant, but didn't find them too convenient for my purposes. I might have been using them wrong, but I found myself having to write the exact same functions with multiple signatures, of the form:

Algebraic!(string, bool, int) value;        

this(string defaultValue) { this.value=defaultValue; }
this(  bool defaultValue) { this.value=defaultValue; }
this(   int defaultValue) { this.value=defaultValue; }

Which hardly seems like working smart.

What would be the correct way to proceed ?

Thanks for the help !

1
FWIW, here is how I implemented an argument parser in D: blog.thecybershadow.net/2014/08/05/ae-utils-funopt - Vladimir Panteleev
Is there a common interface between them at all? - Adam D. Ruppe
You can create a base type like ArgBase, that has the shared functionality that you need, and make the Arg template class inherit from it. If you need to use functionality of some args that other do not share then your only option is to cast--this is not a language limitation, it is just not possible while being statically typed. - Lupus Ossorum
I did try this, but it comes down to just using Object, because I'm using the (templated) value field all the time. So I'd end up casting my ArgBase to Arg(T) all the time nonetheless. - Moonie

1 Answers

2
votes

What exactly is wrong with using std.variant? Instead of writing all those different overloads, you can simply use templated functions:

class Arg
{
    private Algebraic!(string, bool, int) value;

    this(T)(T defaultValue) if (is(T : string) || is(T : bool) || is(T : int))
    {
        this.value = defaultValue;
    }
}