vendredi 21 décembre 2018

Refactor C function input-output and input-destroyed parameters in C++11

I have to work with a couple of legacy C-functions that as usual, have input, input-output, and output parameters and I want to wrap them in a C++ function.

The C function looks like this:

void Cfun(double* in_a, double* io_b, double* out_c, double* in_destr_d){}

In the documentation it says that the first parameter a is an input parameter, b is input-output, c is output and d is a an input parameter that on return will contain an unspecified (garbage) value. (and all the pointers must be non-null.)

In C++ is obvious that one can refactor Cfun without loss in the following way:

double fun(double const& a, double& b, double& d){
    double c;
    Cfun(const_cast<double*>(&a), &b, &c, &d);
    return c;
}

That is, a is promoted to a const reference, b and d are promoted to a reference and c is promoted to a return value.

This function can be used as this:

double a = 1.1;
double b = 1.2;
double d = 1.3;

i)

...
double c1 = fun(1.1, b, d); 

ii)

...
double c1 = fun(a  , b, d);
// b has useful information here
// d has garbage

That is the second and third argument must be l-values, which is fine.

However there is a subtle difference between the parameters b and d. While b has useful information d is essentially destroyed and using its value after the function call would be an error in any case.

This situation reminded me of r-value reference and move in C++11. Where moved r-value parameters end up being in unspecified but assignable states. This makes me think that an input-to-garbage parameter in C can be more or less mapped to r-value reference.

In other words, the wrapper can be written in this way to convey that the value of d, not only will change but it will be left in an unusable state.

double fun2(double const& a, double& b, double&& d){
    double c;
    Cfun(const_cast<double*>(&a), &b, &c, &d);
    return c;
}

Does this sound reasonable, am I lossing something by defining this wrapper? Is something like this practiced in a similar context? Does this justify an r-value to a built-in type (like double here)?

A slight problem I find is that the syntax will become more verbose, albeit more clear from the point of view of the usability of d after the function call.

As far as I can see now the possible syntaxes change to:

i) ... // double c = fun2(1.1, b, d); // now compile error, d is not a r-value double c = fun2(1.1, b, std::move(d));
// obvious error to use the value of d, since it was (probably) moved. However "moving a POD??" d = 2.; // assignment is ok

ii)

...
double c = fun2(1.1, b, 1.3); // good, I can pass a literal now, nothing to misuse

iii)

...
double c = fun2(1.1, b, double{d}); // not clear, reaction "this seems a redudant cast"

iv)

...
double c = fun2(1.1, b, copy(d)); // verbose, but "d should be fine because it was copied"

where template<class T> T copy(T&& t){return t;}

In summary, is fun2 a better wrapper to Cfun than fun?

Aucun commentaire:

Enregistrer un commentaire