I have two families of functions which update variables in two different ways:
- Some functions called to give some change which should be then applied by a client code (
calculate
in the code below); - Others “directly” change the values through the pointers (
update
in the code below).
I want to unify work with these families when it comes to call them for ranges (for sake of simplicity I omitted ranges usage here and just pass a single value).
Everything worked just fine until the moment when developer in client code mistakenly “kept” parameter in lambda, misguiding my process
function.
See the code:
#include <type_traits>
#include <iostream>
void process(float& value, auto fn) {
if constexpr (std::is_invocable_r_v<float, decltype(fn)>) {
std::cout << "Add a change" << std::endl;
value += fn();
}
else {
std::cout << "Update the value" << std::endl;
fn(value);
}
}
void update(float*v)
{
*v += 1.0f;
}
float calculate()
{
return 1.0f;
}
int main() {
float value = 0.0f;
// Process by adding a change
process(value, []() { return calculate(); });
// Process by updating the value
process(value, [](auto& v) { update(&v); });
// User mistakenly kept parameter 'auto&' and used lambda with calculate call,
// so the result will be lost in void process(...), since it will apply "Update the value" branch
process(value, [](auto&) { return calculate(); });
}
As the result in the last process call, the process
was called with lambda which takes a parameter and thus, lead to fn(value)
call with losing of calculate
result.
The question is, can I make a kind of a check in process
which will ensure that if some callable passed as fn
takes a parameter of float*
type, it mustn’t return a value (return type should be void) so that it won’t be lost?
Any kind of compile-time error or assert would work.
I seem that I should play with this std::is_invocable_r_v<float, decltype(fn)
somehow, including the type of the argument, but I can’t figure out the way.
For sure I can write a wrapper which will bring all calculate
functions to update
functions, which is easier and safer, I guess, but still, can this be done without such a wrapper?
1