Allow multiple signatures for lambda/callback function as template parameter - c++

I want to allow multiple signatures for a callable whose type is specified as a template parameter. More specifically, I have a templated update method, which takes a callable that must return a float, and uses it to update values in a grid of data. A simplified illustration of this is
template <typename Fn>
void update(Fn&& fn_)
{
for (Vec3 pos : m_grid)
{
float val = fn_(pos, m_grid)
m_grid(pos) = val;
...
In the above case, the signature of the fn_ must always have both a pos and a grid as parameters, even if they are ignored in the implementation
I would like to use some template magic to allow multiple permutations of callback signatures.
In particular, I would like to allow callbacks that take pos but not grid, and callbacks that take no parameters at all. I don't mind whether the ordering of parameters is enforced or not.
Anyone any hints on how to do this? I don't mind using boost or other libraries, but they should be header-only.

You could do this with a helper function using SFINAE using is_invocable (C++17: std::is_invocable, or earlier with boost: boost::callable_traits::is_invocable)
template <typename Fn,
std::enable_if_t<std::is_invocable<Fn, Vec3, Grid>::value>* = nullptr>
float call_helper(Fn&& fn_, const Vec3& pos_, const Grid& grid_)
{
return fn_(pos_, grid_);
}
template <typename Fn,
std::enable_if_t<std::is_invocable<Fn, Vec3>::value>* = nullptr>
float call_helper(Fn&& fn_, const Vec3& pos_, const Grid& grid_)
{
return fn_(pos_);
}
template <typename Fn,
std::enable_if_t<std::is_invocable<Fn, Grid>::value>* = nullptr>
float call_helper(Fn&& fn_, const Vec3& pos_, const Grid& grid_)
{
return fn_(grid_);
}
template <typename Fn>
void update(Fn&& fn_)
{
for (Vec3 pos : m_grid)
{
float val = call_helper(fn_, pos, m_grid)
m_grid(pos) = val;
...

In particular, I would like to allow callbacks that take pos but not grid, and callbacks that take no parameters at all.
Just define two overloads and use lambdas to do that by forwarding the request to the complete function and thus filtering the extra parameters.
As a minimal, working example:
struct S {
template <typename Fn>
auto update(Fn &&fn_)
-> decltype(fn_(0, 0), void())
{
// ...
fn_(0, 0);
// ...
}
template <typename Fn>
auto update(Fn &&fn_)
-> decltype(fn_(0), void())
{ update([&fn_](auto a, auto) { fn_(a); }); }
template <typename Fn>
auto update(Fn &&fn_)
-> decltype(fn_(), void())
{ update([&fn_](auto, auto) { fn_(); }); }
};
int main() {
S s;
s.update([](auto, auto) {});
s.update([](auto) {});
s.update([]() {});
}

Related

Transform each of parameter pack's values based on a boolean criteria

I am trying to solve this problem in C++ TMP where in i need to convert one parameter pack types into another, and then convert back the types and also values. The conversion back part is based on a boolean criteria that whether an arg in Args... was transformed or not in the first place.
Basically, i have a pack(Args...). First, i transform this (for each args[i], call a transform function). It works like this:
For each arg in Args..., just create same type in transformed_args... unless it is one of following, in that case do following conversions:
Type In Args...
Type In transformed_Args...
SomeClass
shared_ptr to SomeClass
std::vector of SomeClass
std::vector of shared_ptr to SomeClass
everything else remains the same for ex:
int remains int
std::string remains std::string
I achieve this by template specialization, of course
For the next part, i take transformed_args..., publish a class and a functor. I receive call back on this functor from(C++generated Python using Pybind, not important though). Relevant bits of that class look like this...
template<typename C, typename...transformed_args..., typename... Args>
class SomeTemplateClass
{
MethodWrapper<C,void, Args...> func;
//.....
void operator()(transformed_args... targs)
{
//....
(*func.wrapped_method_inside)(transform_back_magic(targs)...) // this is want i want to achieve.
//transform_back_magic(targs)... is a plaeholder for code that checks if type of args[i]... != type of targs[i]... and then calls a tranform_back specialization on it else just return args[i].val
}
}
targs are in transformed_args... format, but underlying C++ function they are aimed for expects Args...
template<typename... Args, typename... transformed_args, ........whatever else is needed>
transform_back_magic(....)
{
if(Args[i].type != transformed_args[i].types)
tranform_back(targs[i]...);
}
the tranform_back function template logic is specialized for different cases and all logic is in place. But how to invoke that based on this boolean criteria is hitting my TMP knowledge limits. I just got started not many weeks ago.
Here i am listing down what i have created so far.
First of all this is what i need in pseudo code
template<typename C, typename... transformed_args, typename... Args>
class SomeTemplateClass
{
MethodWrapper<C,void, Args...> func;
void operator(transformed_args... targs)
{
**//In pseudo code, this is what i need**
Args... params = CreateArgsInstanceFromTransformedArgs(targs);
(*func.wrapped_method_inside)(params...);
}
}
In my attempt to implement this, so far I have decided on creating a tuple<Args...> object by copying data from targs(with conversions where ever required)
void operator(transformed_args... targs)
{
//....
auto mytup = call1(std::tuple<args...>(), std::make_index_sequence<sizeof...(Args)>,
std::make_tuple(targs...), targs...);
// mytup can be std::tuple<Args...>(transform_back(1st_targs), transform_back(2nd_targs)....). Once available i can write some more logic to extract Args... from this tuple and pass to(*func.wrapped_method_inside)(....)
(*func.wrapped_method_inside)(ArgsExtractorFromTuple(mytup)); // this part is not implemented yet, but i think it should be possible. This is not my primary concern at the moment
}
//call1
template<typename... Args, typename... Targs, std::size_t... N>
auto call1(std::tuple<Args...> tupA, std::index_sequence<N>..., std::tuple<Targs...> tupT, Targs ..)
{
auto booltup = tuple_creator<0>(tupA, tupT, nullptr); // to create a tuple of bools
auto ret1 = std::make_tuple<Args...>(call2(booltup, targs, N)...); // targs and N are expanded together so that i get indirect access to see the corresponding type in Args...
return ret1;
}
// tuple_creator is a recursive function template with sole purpose to create a boolean tuple.
// such that std::get<0>(booltup) = true,
//if tuple_element_t<0,std::tuple<Args...>> and tuple_element_t<0,std::tuple<targs...>> are same types else false
template<size_t I, typename... Targs, typename... Args>
auto tuple_creator(std::tuple<Args...>tupA, std::tuple<Targs...>tupT, std::enable_if_t<I == sizeof...(targs)>*)
{
return std::make_tuple(std::is_same<std::tuple_element_t<I-1, std::tuple<Targs...>>, std::tuple_element_t<I-1, std::tuple<Args...>>>::value);
}
template<size_t I = 0, typename... Targs, typename... Args>
auto tuple_creator(std::tuple<Args...>tupA, std::tuple<Targs...>tupT, std::enable_if_t<I < sizeof...(targs)>*)
{
auto ret1 = tuple_creator<I+1>(tupA, tupT, nullptr);
if(!I)
return ret1;
auto ret2 = std::is_same<std::tuple_element_t<I-1, std::tuple<Targs...>>, std::tuple_element_t<I-1, std::tuple<Args...>>>::value;
return std::tuple_cat(ret1, std::make_tuple(ret2));
}
template<typename TT, typename Tuple>
auto call2(Tuple boolyup, TT t, std::size_t I)
{
auto ret = transform_back<std::get<I>(booltup)>(t); // error: I is not a compile time constant
return ret;
}
transform_back is a template that uses a bool template param and enable_if based specialization to decide whether transform an argument back or not
below are the transform_back specialization for std::vector. Similarly i have others for when T = Class etc and so on
template<bool sameTypes, typename T>
std::enable_if_t<(is_vector<T>::value, is_shared_ptr<typename T::value_type>::value &&
is_class<remove_cvref_t<typename T::value_type_element_type>>::value
&& sameTypes), T>
transform_back(T val) // it was never transfoemd in first place, return as is
{
return val;
}
template<bool sameTypes, typename T>
std::enable_if_t<(is_vector<T>::value, is_shared_ptr<typename T::value_type>::value
&& is_class<remove_cvref_t<typename T::value_type_element_type>>::value
&& !sameTypes),
typename std::vector<typename T::value_type::element_type>>
transform(T val)
{
std::vector<T::value_type::element_type> t;
for(int i = 0 ; i < val.size(); ++i)
{
typename T::value_type::element_type obj = *val[i];
t.push_back(obj);
}
return t;
}
Both these specialization are same and only differ on sameTypes boolean variable
This code currently errors out in call2 method while trying to using
std::get
auto ret = transform_back<std::get<I>(booltup)>(t); // error: I is not a compile time constant
How can you help?
1)What could be the work around to std::get issue here? Just cant figure out a way to fit in std::size_t as template arg here instead of function arg to make it work at compile time.
Other than this:
2)If you can suggest an alternative approach to implement from top level.
Args... params = CreateArgsInstanceFromTransformedArgs(targs);
That would be great. The path i took is not very convincing personally to me.
If I understand correctly, you might do something like:
template <typename> struct Tag{};
std::shared_ptr<SomeClass> transform_to(Tag<std::shared_ptr<SomeClass>>, const SomeClass& s)
{
return std::make_shared<SomeClass>(s);
}
std::vector<std::shared_ptr<SomeClass>> transform_to(Tag<std::vector<std::shared_ptr<SomeClass>>>, const std::vector<SomeClass>& v)
{
std::vector<std::shared_ptr<SomeClass>> res;
res.reserve(v.size());
for (const auto& s : v) {
res.emplace_back(std::make_shared<SomeClass>(s));
}
return res;
}
const SomeClass& transform_to(Tag<SomeClass>, const std::shared_ptr<SomeClass>& s)
{
return *s;
}
std::vector<SomeClass> transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
{
std::vector<SomeClass> res;
res.reserve(v.size());
for (const auto& s : v) {
res.emplace_back(*s);
}
return res;
}
template <typename T>
const T& transform_to(Tag<T>, const T& t) { return t; } // No transformations
And then
std::function<void (Args...)> func;
template <typename ... transformed_args>
void operator () (transformed_args... targs) const
{
func(transform_to(Tag<Args>(), targs)...);
}
Just explaining the use case here to add some context. Consider these three methods in C++ each represented with the function pointer SomeTemplateClass::func:
void foo(vector<shared_ptr<SomeClass>>) // 1
// Args... = vector<shared_ptr<SomeClass>>, Targs... = vector<shared_ptr<SomeClass>>
void foo(vector<SomeClass>) // 2
// Args... = vector<SomeClass>, Targs... = vector<shared_ptr<SomeClass>>
void foo(vector<SomeClass>, vector<shared_ptr<SomeClass>>) // 3
// Args... = vector<SomeClass>, vector<shared_ptr<SomeClass>>, Targs... = vector<shared_ptr<SomeClass>>, vector<shared_ptr<SomeClass>>
One instance each of SomeTemplateClass is exposed to Python via Pybind. I do these transformations so that when foo is called from Python, any arg vector<T>(in C++) is received as vector<shared_ptr<T>> in SomeTemplateClass functor. This helps in to get handle to previously created objects T that i need.
But as you can see from 3 cases for foo, foo(vector<shared_ptr<T>>) does not need to be transformed to and subsequently not need to be transformed back. The case of 'tranform_to'is easily handled with template specialization, but while transforming back, vector<shared_ptr<T>> cant be blindly converted back to vector<T>. So (transform(targs...)) needs an additional logic to transform a particular arg (or targ) only when targ[i]::type != arg[i]::type
Building on Jarod's answer, i rather need something like this where in transform_to method for vector<shared_ptr> is further divided in two possible templates
template<bool wasOriginallyTransformed>
enable_if<!wasOriginallyTransformed, std::vector<std::shared_ptr<SomeClass>> transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
{
return v;
}
template<bool wasOriginallyTransformed>
enable_if<!wasOriginallyTransformed, std::vector<<SomeClass>
transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
{
std::vector<SomeClass> res;
res.reserve(v.size());
for (const auto& s : v) {
res.emplace_back(*s);
}
return res;
}

C++ concept to inline lambda parameter

I'm am trying to pass a lambda (that captures local context by reference) to a function and ensure it is inlined.
I think a solution would be to use a function template and a C++20 concept, but I don't know how to write the concept of a lambda that takes specific types.
The aim is to factorize a complex loops on raster files, and let the caller execute code for each pixel.
The lambda call must be inlined.
I had bad luck with:
std::function: the performance with lambda is 2x worse than writing the whole loop in the caller code.
function pointer: lambda with capture (this or local variable) cannot be casted to function pointer
template method: I cannot overload it, and cannot check the lambda type as easily as with previous solutions.
Working code with bad performance:
// loop
template<typename T>
class Raster<T>
{
template<typename FileDataType>
void SaveToDisk(std::ostream& file, const std::function<FileDataType(T&)>& callback) const;
template<typename FileDataType>
void SaveToDisk(std::ostream& file, AnotherCallbackType...);
}
// call
class GeoRaster<double> : public Raster
{
void Test()
{
// Creates a file containing "short" values, from a GeoRaster<double>
SaveToDisk<short>(file, [&](double val)
{
// this lambda call must be inlined
if (val == this->nodata)
{
return 255;
}
else
{
return short(val) / 16;
}
}
}
});
Could you help me to write a concept like this:
template<typename T, typename FileDataType>
concept SimpleWriteCallback = requires(T a) {
{ a } -> std::convertible_to<std::function<FileDataType(T&)>>;
};
template<typename FileDataType, SimpleWriteCallback<FileDataType> Callback>
void SaveToDisk(std::ostream& file, Callback& callback) const;
I think that you are looking for is std::invocable, which is a concept that allows you to enforce that a type can be called with a given set of arguments:
#include <concepts>
#include <cstdio>
void do_stuff(std::invocable<int, int> auto &&fn) {
fn(3, 4);
}
int main() {
do_stuff([](const int a, const int b) { std::printf("%d %d\n", a, b); });
// do_stuff([] { std::puts("hi!"); }); // won't compile
return 0;
}
If you need to also constrain the return type of Func, you may use a requires() block to add a compound requirement:
template <typename Func>
requires requires(Func &&fn, std::vector<int> v) {
std::invocable<Func, const std::vector<int>&, bool>;
{ fn(v, false) } -> std::convertible_to<int>;
}
int do_stuff(std::vector<int> v, Func &&fn) {
v.push_back(31);
return fn(v, true);
}
int main() {
do_stuff({1, 2 ,3}, [](const auto &, bool) { return 3; }); // ok: right parameters, and return type is convertible to int
do_stuff({1, 2 ,3}, [](const auto &, bool) { return 3.f; }); // ok: float is also implicitly convertible to int
//do_stuff({1, 2 ,3}, [](const auto &v) { return 3; }); // won't compile, cannot be invoked with (const vector<int>&, bool)
//do_stuff({1, 2 ,3}, [](const auto &v, bool) { return ""; }); // won't compile, const char (&)[] is not convertible to int
}
First, we have to start with how we're even thinking about the constraint:
template<typename T, typename FileDataType>
concept SimpleWriteCallback = /* ... */;
We're not constraining the callback on a FileDataType, FileDataType is what the callback returns. Rather, we're constraining it on the type that we're passing into it:
template <typename F, typename T>
concept SimpleWriteCallback = std::invocable<F&, T&>;
And you'd then use this concept like:
template <SimpleWriteCallback<T> F>
void SaveToDisk(std::ostream& file, F&& callback) const {
using FileDataType = std::invoke_result_t<F&, T&>;
// ...
}
Now, separately, if you want to override the actual return type (as you seem to do, since the lambda returns an int but you want a short), you can provide an overload that lets you provide the type explicitly and have the other overload just forward to it:
template <typename FileDataType, SimpleWriteCallback<T> F>
requires std::convertible_to<std::invoke_result_t<F&, T&>, FileDataType>
void SaveToDisk(std::ostream& file, F&& callback) const {
// ...
}
template <SimpleWriteCallback<T> F>
void SaveToDisk(std::ostream& file, F&& callback) const {
// by default, FileDataType is the actual invocation result
SaveToDisk<std::invoke_result_t<F&, T&>>(file, callback);
}

C++ Convert tuple of homogeneous wrapped type to tuple of raw type

I'd like to call std::apply() to a function; however, I am unable to because the std::tuple I use is currently wrapped. For example:
#include <tuple>
template <class T>
struct wrapped
{
wrapped(T t) : t(t) {}
T t;
};
template <class T, class ... Args>
struct delay_call
{
T(*callback)(Args...);
std::tuple<Args...> params;
delay_call(T(*callback)(Args...), Args ... params) :
callback(callback), params(params...)
{}
T call()
{
return std::apply(callback, params);
}
};
template <class T, class ... Args>
struct delay_call_with_wrap
{
T(*callback)(Args...);
std::tuple<wrapped<Args>...> w_params;
delay_call_with_wrap(T(*callback)(Args...), wrapped<Args> ... w_params) :
callback(callback), w_params(w_params...)
{}
T call()
{
std::tuple<Args...> params; // = w_params.t
return std::apply(callback, actual_params);
}
};
float saxpy(float a, float x, float y)
{
return a * x + y;
}
int main()
{
float a = 1, x = 2, y = 3;
delay_call delay_saxpy(saxpy, a, x, y);
wrapped w_a = 1.f, w_x = 2.f, w_y = 3.f;
delay_call_with_wrap w_delay_saxpy(saxpy, w_a, w_x, w_y);
float result = delay_saxpy.call();
float w_result = w_delay_saxpy.call();
}
the delay_call struct works as expected; however, I am unsure how to go about extracting the actual value of each tuple element and giving that to std::apply() to execute.
In short, for delay_call_with_wrap::call, how would I convert std::tuple<wrapped<Args>...> to a std::tuple<Args...>?
I would avoid std::apply completely and call the callback directly by unpacking the tuple using std::index_sequence:
template <std::size_t ...I> T call_low(std::index_sequence<I...>)
{
return callback(std::get<I>(w_params).t...);
}
T call()
{
return call_low(std::make_index_sequence<sizeof...(Args)>{});
}
In short, for delay_call_with_wrap::call, how would I convert std::tuple<wrapped<Args>...> to a std::tuple<Args...>?
It seems to me is better avoid std::apply() using the old std::make_index_sequence/std::index_sequence way (see HolyBlackCat answer).
But, if you really want to use std::apply(), you can call it a first time to unwrap the tuple (to get a tuple of unwrapped values) and then call is as usual.
I mean something as
T call ()
{
auto actual_params = std::apply([](auto ... wt){ return std::make_tuple(wt.t...); },
w_params);
return std::apply(callback, actual_params);
}
or, in a single call, directly
T call()
{
return std::apply(callback,
std::apply([](auto ... wt){ return std::make_tuple(wt.t...); },
w_params));
}
This solution is reasonable, IMHO, if the w_param member is constant so you can calculate the actual_params one time for all and make it static
Probably not the best solution for use in practice, but here's one using a variadically templated lambda to avoid index_sequence:
template <class T, class ... Args>
struct delay_call_with_wrap
{
T(*callback)(Args...);
std::tuple<wrapped<Args>...> w_params;
delay_call_with_wrap(T(*callback)(Args...), wrapped<Args> ... w_params) :
callback(callback), w_params(w_params...)
{}
T call()
{
auto helper = [this] <class ... Args_> (wrapped<Args_>... args)
{
return callback(args.t...);
};
return std::apply(helper, w_params);
}
};
Demo
The idea is to just provide a function that matches the arguments that std::apply yields here - it needs to take wrapped<Args>.... From there it's trivial to expand the pack while extracting the wrapped value.
We use a lambda because std::apply wants a Callable, so we can't just use another member function. Well, I guess we could overload operator() for delay_call_with_wrap. That would be mildly confusing but at least not limited to C++2a (and missing compiler support) like templated lambdas.

C++ can't derive template parameters for high-order functions

When I use have template function which accepts another function as a parameter, C++ can't derive template parameters. It's very annoying to specify them all the time. How can I define the following function such that I don't have to specify type parameters every time?
#include <functional>
template <typename S, typename T>
T apply(const S& source, const function<T (const S&)>& f) {
return f(source);
}
template <typename S, class Functor, typename T>
T applyFun(const S& source, const Functor& f) {
return f(source);
}
int main() {
// Can't derive T. Why?
apply(1, [](int x) { return x + 1; });
// Compiles
apply<int, int>(1, [](const int& x) { return x + 1; });
// Can't derive T. Kind of expected.
applyFun(1, [](int x) { return x + 1; });
}
It makes sense to me why it can't derive type parameter in the second function, but not in the first one (since x + 1 is int, so it should deduce that T = int).
A template parameter must appear in a function parameter type to be deductible. Moreover lambdas are not functions so, whatsoever the return type of a lambda cannot participate to template argument deduction.
But in this case, there is no need to specify the return type. Return type deduction can do the job:
template <typename S, class Functor>
auto applyFun(const S& source, const Functor& f) {
return f(source);
}
If you can use C++17, you can use the deduction guides for std::function as follows
template <typename S, typename F,
typename T = typename decltype( std::function{std::declval<F>()} )::result_type>
T applyFun (S const & source, F const & f)
{
return f(source);
}
but, as pointed by Oliv, for your example function there is non need of T because you can use auto (from C++14; auto ... -> decltype(f(source)) in C++11).
-- EDIT --
The OP say
The good thing about this solution is that I can use T inside the function (e.g. if I want to implement vector_map).
You can detect and use T, also inside the function, using a using
Something as
template <typename S, typename F>
auto applyFun (S const & source, F const & f)
{
using T = typename decltype( std::function{f} )::result_type;
return f(source);
}
or simpler: using T = decltype( f(source) );.
The OP also observe that
The downside is that for some reason now I can't write [] (const auto& x) { ... } in function call.
Correct.
Because std::function template types can't be deduced from a generic-lambda.
But using the fact that you know the type of the argument, you can use decltype() again
template <typename S, typename F,
typename T = decltype(std::declval<F const>()(std::declval<S const>()))>
T applyFun (S const & source, F const & f)
{ return f(source); }
This solution should works also for C++14 and C++11.

Passing references to a variadic-templates use the std::reference-wrapper

I try to pass to a variadic template function a list of references and pass it to another function. The code that I wrote is the following:
template <typename T>
void fun(cv::Point_<T> & pt) { pt.x++; pt.y++; }
template <class ... args>
void caller(args & ... list) {
typedef typename std::tuple_element<0, std::tuple<args...> >::type T;
std::array<std::reference_wrapper<T>, sizeof...(list)> values {list ... };
for(int i=0; i<values.size(); i++)
fun(values[i]);
}
then I call the function caller in this way:
cv::Point2f a, b, c;
caller(a, b, c);
the compiler give me the following error:
No matching function for call to 'fun'
Candidate template ignored: could not match 'Point_' against 'reference_wrapper'
what I missing?
Although std::reference_wrapper<T> has an implicit conversion to T&, you cannot use both an implicit conversion and template argument deduction at the same time, and template argument deduction is necessary to call fun.
Try
fun(values[i].get());
Even simpler is
template <typename...Args>
void caller(Args&...args)
{
auto tmp = { (func(args),0)..., 0 };
}
This uses the fact that parameter pack expansion can occur in braced init lists. Since func() returns void, we cannot simply use { func(args)... }, but use (func(args),0) to have an int. Finally, the last 0 is to ensure that the code compiles (and does nothing) in case of an empty parameter pack.
You can generalise this and write a template that calls a given generic function for every element of a pack:
template <typename Func, typename...Args>
void call_for_each(Func &&func, Args&&...args)
{
auto unused = { (func(std::forward<Args>(args)),0)...,0 };
}
which may be used like this (C++14)
int main()
{
int a=1;
double b=2.4;
auto func = [](auto&x) { std::cout<<' '<<x++; };
call_for_each(func,a,b);
std::cout<<'\n';
call_for_each(func,a,b);
std::cout<<'\n';
}
This uses a C++14 lambda (taking an auto argument). Note that the parameter pack must come last among the template parameters of call_for_each.
Since the goal of this might be to iterate over all args, here's a more generic solution. We are going to implement for_pack:
template<typename... Args, typename F>
void for_pack(F function, Args&&... args) {
using expand = int[];
(void)expand{(function(std::forward<Args>(args)), void(), 0)..., 0};
}
This will execute function for every args in Args.
Now, your function caller is much more trivial to implement:
template <typename... args>
void caller(args&... list) {
for_pack([&](cv::Point_<T>& arg){
fun(arg);
}, list...);
}
Since a google search for "c++ pass reference parameters to variadic template" gives this as first result, I'll put this generic solution here.
struct HH { /*...*/ void change_me() { /*...*/ } };
template<typename...T> void parms_r_refs() {}
template<typename H, typename...T> void parms_r_refs(H &h, T&...t) { h.change_me(); parms_r_refs(t...); }
template<typename...T> void parms_r_refs(T&...t) { parms_r_refs(t...); }
HH a, b, c;
..
parms_r_refs(a, b, c);
..