I implemented a getop()
in C++. This is a class with all sorts of bells and whistles.
One of those bells would be a way to verify that the user enters valid options at compile time. Already I have part of that for the flags (not shown here) but I've been trying to make it work for the struct option
and at this time I've not had much success.
First of all, the code allows me to define an array of options using the define_option()
function. I want to be able to specify the fields of the option structure in any order and have some sanity checks on them. So I use a list of arguments to a template function. Each argument is a class which gets instantiated on the spot and then the define_option()
selects the correct class for each field, extracting the value.
Up to here, everything works like a charm. Pretty basic C++.
Where I'm having a problem is inside the define_option()
class. Say for instance that I were to try the following:
... define_option(...)
{
...
option opt = { ... };
static_assert(opt.f_short_name != '\0', "this failed");
...
}
Here I get an error. static_assert()
tells me that opt
is not constant.
a.cpp:168:1: error: non-constant condition for static assertion
So next my step is to mark the opt
variable a constexpr
(something the error says to do to resolve the static_assert()
.)
... define_option(...)
{
...
constexpr option opt = { ... };
...
}
Now even without the static_assert()
, the code doesn't compile. I get the following error:
a.cpp:168:5: error: ‘args#0’ is not a constant expression
I'm thinking that it's not really doable from within the define_option()
function, even though it's possible to check the resulting list of options as shown in the working example below. So in the end the opt
global variable is a static fully defined at compile time (no code necessary to re-work on the table at runtime) but that doesn't help me since I can't make sure that the arguments for each option will work as expected.
I have an online running sample on coliru.
// compiled with: g++ -std=c++14 -o a ~/tmp/a.cpp
//
#include <iostream>
typedef char32_t short_name_t;
constexpr short_name_t NO_SHORT_NAME = L'\0';
struct option
{
short_name_t f_short_name = NO_SHORT_NAME;
char const * f_name = nullptr;
};
template<typename T, T default_value>
class OptionValue
{
public:
typedef T value_t;
constexpr OptionValue<T, default_value>()
: f_value(default_value)
{
}
constexpr OptionValue<T, default_value>(T const v)
: f_value(v)
{
}
constexpr value_t get() const
{
return f_value;
}
private:
value_t f_value;
};
class ShortName
: public OptionValue<short_name_t, NO_SHORT_NAME>
{
public:
constexpr ShortName()
: OptionValue<short_name_t, NO_SHORT_NAME>()
{
}
constexpr ShortName(short_name_t name)
: OptionValue<short_name_t, NO_SHORT_NAME>(name)
{
}
};
class Name
: public OptionValue<char const *, nullptr>
{
public:
constexpr Name()
: OptionValue<char const *, nullptr>()
{
}
constexpr Name(char const * name)
: OptionValue<char const *, nullptr>(name)
{
}
};
template<typename T, typename F, class ...ARGS>
constexpr typename std::enable_if<std::is_same<T, F>::value, typename T::value_t>::type find_option(F first, ARGS ...args)
{
return first.get();
}
template<typename T, typename F, class ...ARGS>
constexpr typename std::enable_if<!std::is_same<T, F>::value, typename T::value_t>::type find_option(F first, ARGS ...args)
{
return find_option<T>(args...);
}
template<class ...ARGS>
constexpr option define_option(ARGS ...args)
{
option opt =
{
.f_short_name = find_option<ShortName >(args..., ShortName()),
.f_name = find_option<Name >(args...),
};
return opt;
}
constexpr option const opt[] =
{
define_option(
Name("--help"),
ShortName('h')
)
};
// this test works as expected here
// but it does not work from inside define_option()
//
static_assert(opt[0].f_name[0] != '-', "Wrong start?!");
int main(int argc, char * argv [])
{
std::cerr << "opt[0].f_short_name = " << opt[0].f_short_name << std::endl;
return 0;
}
At this time I'm limited to C++14. If there is a C++17 solution, I'd still be interested.
Just add something that makes the call non-constant. Say, throw an exception.
template<class ...ARGS>
constexpr option define_option(ARGS ...args)
{
option opt =
{
.f_short_name = find_option<ShortName >(args..., ShortName()),
.f_name = find_option<Name >(args...),
};
if(/* check fails */) throw something;
return opt;
}
Then when you try to do
constexpr option const opt[] =
{
define_option(
Name("--help"),
ShortName('h')
)
};
and the check fails, the compiler will complain that this call to define_option
is not a constant expression and cannot be used to initialize a constexpr
variable.
You can even rig something
to produce a linker error if this function is ever called at runtime. See e.g. this question.