There was a random
function added to ExprTk table before:
exprtk::symbol_table<double> symbolTable;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> dis(0.0, 1.0);
auto randomFunc = []() {
return dis(gen);
};
symbolTable.add_function("random", randomFunc);
exprtk::expression<double> expression;
expression.register_symbol_table(symbolTable);
Now I modified it to take minimum and maximum args:
exprtk::symbol_table<double> symbolTable;
std::random_device rd;
std::mt19937 gen(rd());
auto randomFunc = [](double min = 0, double max = 1) {
std::uniform_real_distribution<double> dis(min, max);
return dis(gen);
};
symbolTable.add_function("random", randomFunc);
exprtk::expression<double> expression;
expression.register_symbol_table(symbolTable);
It works, except for keeping compatibility with the old argument-less random
usage, which is desired. Despite having default args, ExprTk can't compile random
or random()
. I'm assuming the library just handles the number of arguments and doesn't check if there are any default values.
I think I would need to instead switch to ivararg_function
to support this. Or is there a simplier way I'm missing?
Having default values specified for the function parameters does not change the fact that the function you are registering is a two input parameter function, requiring two parameters be passed when invoking it in an expression/program.
In short what you're looking for is called: function overloading and this can be achieved by using an exprtk::igeneric_function
.
The two overloads you're interested in are:
The following demonstrates how such a exprtk::igeneric_function
can be implemented:
template <typename T>
struct unirandom_dist final : public exprtk::igeneric_function<T>
{
typedef exprtk::igeneric_function<T> igenfunct_t;
typedef typename igenfunct_t::generic_type generic_t;
typedef typename igenfunct_t::parameter_list_t parameter_list_t;
typedef typename generic_t::scalar_view scalar_t;
unirandom_dist()
: exprtk::igeneric_function<T>("Z|TT")
/*
Overloads:
0. Z - Zero parameters, range is [0,1)
1. TT - min and max, range is [min,max)
*/
{
std::random_device device;
std::array<unsigned int,std::mt19937::state_size> seed;
std::generate_n(seed.data(),seed.size(),std::ref(device));
std::seed_seq seq(std::begin(seed),std::end(seed));
generator.seed(seq);
}
using igenfunct_t::operator();
T operator() (const std::size_t& ps_index, parameter_list_t parameters) override
{
const T min = (ps_index == 0) ? T(0) : scalar_t(parameters[0])();
const T max = (ps_index == 0) ? T(1) : scalar_t(parameters[1])();
if (min <= max)
{
std::uniform_real_distribution<T> unidist(min,max);
return unidist(generator);
}
return std::numeric_limits<T>::quiet_NaN();
}
std::mt19937 generator;
};
The following is how all of it can be brought together and used in an expression:
template <typename T>
void foo()
{
typedef exprtk::symbol_table<T> symbol_table_t;
typedef exprtk::expression<T> expression_t;
typedef exprtk::parser<T> parser_t;
unirandom_dist<T> unirnd;
exprtk::rtl::io::package<T> io_package;
symbol_table_t symbol_table;
symbol_table.add_function("random" , unirnd);
symbol_table.add_package (io_package);
expression_t expression;
expression.register_symbol_table(symbol_table);
parser_t parser;
const std::string random_program =
" println('random: ', random ); "
" println('random(): ', random() ); "
" println('random(-1,+1): ', random(-1,+1)); ";
parser.compile(random_program,expression);
expression.value();
}
int main()
{
foo<double>();
return 0;
}
Point of note is that now the function has a few overloads that can be invoked in the following ways:
As a side note the reason you wouldn't use ivararg_function
is because it doesn't come with type checking and will require the implementation of unirandom_dist
to determine in the critpath the number of params being passed and their nature etc.