5

Edit Totally forgot to mention smth that's obvious to me:

a_func() wants to modify the var_xxx, so the solutions that iterate using a const type aren't completely satisfying (although they bring in ideas, for sure).

I feel the answer is simple yet can't find it.

Given:

A number (a small number, like 6, 8, 10) of variables of a user class type (precisely, a math vector object), where each variable has to have a unique name, defined by the algorithm (in order to be calculated and then used independently, and to be able to easily consult the paper that the code is based on)

Do:

In a piece of code, calculate and initialize those variables, referring to them by name; and then in another piece of code perform an operation on them in a sweep, without having to duplicate the code calling the operation (i.e. a function) for each and every variable.

Code goes like this:

// in a class declaration
vector_type var_abc, var_efg, var_xyz;

// in a class member function
// "some_math_xxx" pieces are coded inline, not as separate functions
var_abc = /* some_math_abc */;
var_efg = /* some_math_efg */ ;
var_xyz = /* some_math_xyz */ ;
a_func(var_abc); a_func(var_efg); a_func(var_xyz);

I mean, I can of course type a_func(var_xxx) six or more times. But it, well, feels just wrong to me.

Thought of:

  • a hash to store the names of those cars (bad for the math using them later I guess)
  • a std::array<> of pointers to the vars (bit clumsy but works)
  • this code that fails to compile:

    for (vector_type& vr : { var_abc, var_efg, var_xyz }) { a_func(vr); } (error: binding const vector_type to reference of type vector_type&)

How would you solve this? I'd prefer an elegant solution, of course. The standard better be just C++11, though C++14 is ok too if it enables a super cute solution to this problem.

P.S. The question entering form says "the title is subjective and the Q may be closed". I'm sure this is not an opinion-based Q, as I'm not asking to compare the solutions, but to suggest some.

iksemyonov
  • 4,106
  • 1
  • 22
  • 42
  • I rather like the `std::array<>` solution. – Logicrat Apr 13 '16 at 12:43
  • 1
    for (const vector_type& vr : ? – thorsan Apr 13 '16 at 12:44
  • "How would you" sounds opinion based to me, but the question itself doesn't. Have you considered using `std::tie`? I believe this is one of the cases it was made for. – nwp Apr 13 '16 at 12:44
  • 1
    Your solution would work if you use `std::ref` from ``, e.g. `for (vector_type& vr : { std::ref(var_abc), std::ref(var_efg), std::ref(var_xyz) }) { a_func(vr); }` – Hilborn Apr 13 '16 at 12:45
  • @thorsan it's a modifying operation, unfortunately – iksemyonov Apr 13 '16 at 12:48
  • @nwp could you give an example of how you'd use `std::tie` here? I thought about it but lack the knowledge of this new functionality yet. And , you can't `for_each` over a `std::tuple` easily, can you? – iksemyonov Apr 13 '16 at 12:50
  • I'd like a variadic function template, where the first argument is the function and the rest are variable references – Hilborn Apr 13 '16 at 12:51
  • What am I missing? This seems simple: `for ( auto it: std::initializer_list{&vecA, &vecB, &vecC} ) { doStuff(*it); }` The issue seems to be that you were trying to initialise a reference with a `const` value copied to the implicit `initializer_list`, so just use pointers; they're not to be feared. you probably don't even need to explicitly specify the `initializer_list` type as long as you're not using polymorphic pointers in it. @Hilborn Putting everything through an extra layer of `std::reference_wrapper` seems to be an unnecessary hoop to jump through, just to avoid pointers. – underscore_d Apr 13 '16 at 12:52
  • @Hilborn funny you asked – RamblingMad Apr 13 '16 at 12:58
  • @CoffeeandCode `ref` returns a `reference_wrapper`. – Jonathan Mee Apr 13 '16 at 13:01
  • @JonathanMee I neglected his first comment haha – RamblingMad Apr 13 '16 at 13:02
  • @Hilborn I believe that I have straight up ganked your answer. I thought I could do this either with `vector`s or `ref` but I cannot solve it with a `vector`. If you post an answer let me know and I will delete mine. – Jonathan Mee Apr 13 '16 at 13:03
  • Actually `std::tie` doesn't fit here. It is meant to bundle different types and therefore does not support iterating over. Sorry. – nwp Apr 13 '16 at 13:10
  • @CoffeeandCode haha :) – Hilborn Apr 13 '16 at 13:12
  • 1
    @underscore_d I don't fear pointers, I just wanted to point out that it is possible to iterate over references. It has some syntactic overweight, so I would prefer a variadic template instead. Pointers is a good alternative too – Hilborn Apr 13 '16 at 13:12
  • 1
    @JonathanMee no problem. Yes `std::vector` has issues with references, but then again you can use a vector of `std::reference_wrapper` (or pointers) :) – Hilborn Apr 13 '16 at 13:14
  • @underscore_d Yeah, he was just talking about `vector` in response to my faulty solution. An `initializer_list` is the best solution. – Jonathan Mee Apr 13 '16 at 13:32
  • 1
    re edit: "the solutions that iterate using a const type aren't completely satisfying" - where are these solutions? I'm struggling to find them. Trick question! All the ones I can see use non-`const` pointers or references (or pointers wrapped to look like references). – underscore_d Apr 13 '16 at 14:08

5 Answers5

4

I'd write a recursive function that applies a functor to a set of variables:

#include <functional>

template<typename Functor, typename First, typename Second, typename ... Others>
auto apply_over(Functor &&fn, First &&var0, Second &&var1, Others &&... vars){
    fn(std::forward<First>(var0));
    apply_over(std::forward<Second>(var1), std::forward<Others>(vars)...);
}

template<typename Functor, typename Var>
auto apply_over(Functor &&fn, Var &&var){
    fn(std::forward<Var>(var));
}

And use it like so:

auto main() -> int{
    var_type var0, var1, var2;

    apply_over([](var_type &var){ /* do something with var */ }, var0, var1, var2);
}

Or even better (more generic) with C++14:

auto main() -> int{
    var_type var0;
    int var1;
    float var2;

    // no need to worry about types!
    apply_over([](auto &var){ var += decltype(var)(1); }, var0, var1, var2);
}

Or with your function a_func:

auto main() -> int{
    var_type var0, var1, var2;

    apply_over(a_func, var0, var1, var2);
}
RamblingMad
  • 5,332
  • 2
  • 24
  • 48
4

The issue seems to be that you were trying to initialise a reference with a const value copied to the implicit initializer_list. This container, like all others, cannot contain references. So, instead, you get const values, since initializer_list (currently? and probably forever) only offers const access to its members.

So... just use pointers to the elements. They're not to be feared. This seems simple:

for ( auto const it: /* std::initializer_list<VecType *> */ {&vecA, &vecB, &vecC} ) {
    doStuff(*it);
    // N.B. it is a *const, not a const *const!
}

You don't need to explicitly specify the initializer_list type, as long as you're not using elements of various polymorphic pointer types or that require conversion operators (in which case, you do).

Unless I'm missing something, others' suggestions of putting everything through an extra layer of std::reference_wrapper seems to be an unnecessary hoop to jump through, just to avoid pointers for some reason. Other solutions might invoke copies, which could lead to a lot of confusion.

I kinda wish C++ had a range-for style 'automatic dereference` for this case, but getting that implemented would probably cause all kinds of syntactic ambiguity, which wouldn't really be worth it when it's simple enough this way.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
  • I'm sure you're aware that the compiler is going to simplify this code and `reference_wrapper` code to the same assembly, so really it's more about which approach is more readable. Anyway, +1 for a solution that shows an understanding of the problem and how to fix it. – Jonathan Mee Apr 13 '16 at 13:30
  • @JonathanMee Thanks. Yeah, I assume in most cases, `reference_wrapper` is trivial to optimise away. The "unnecessary hoop" was as much about typing as theoretical overhead. :) As a personal convention, since it's most likely optimised to the same thing, I just go directly to pointers, but maybe that's my C past lingering! The clarity of reference semantics could be useful in more complex examples... unless one is willing to use pointers and take a (de)reference at the start of the loop, haha – underscore_d Apr 13 '16 at 13:37
  • @RichardHodges `std::endl`. Sorry, are we just quoting random names? Can you clarify what you mean? – underscore_d Apr 13 '16 at 14:05
  • std::addressof(x) is preferable to &x - in case the unary & operator has been overloaded. – Richard Hodges Apr 13 '16 at 14:06
  • 1
    @RichardHodges Sure, of course, but for simple cases where we know that overload is absent, it's just unnecessary typing. I would use it where I couldn't be certain, but in every case so far, I can. – underscore_d Apr 13 '16 at 14:07
  • 1
    While I accept your position (and also hate typing), IMHO std::addressof is still preferable since it's explicitly expressive and protects against future interface changes of x; since the standard guarantees that it will always yield the address of x. I find myself using it in place of & in all cases because of this. I liked the simplicity and directness of your answer BTW :) – Richard Hodges Apr 13 '16 at 14:13
  • 1
    @RichardHodges My goodness that's awesome, I didn't even know `addressof` existed. I have learned my new thing for the day! – Jonathan Mee Apr 13 '16 at 14:13
  • 1
    @RichardHodges Nope serious, I basically just learn stuff when I see it on http://www.stackoverflow.com and that's the first I've seen of `addressof`. That's pretty much the reason I get on here is to expand my C++ horizons. – Jonathan Mee Apr 13 '16 at 14:16
  • @RichardHodges Thanks! Your points are extremely valid. For now, I'll accept the risk that I add an `operator&` overload later, forget about this, and spend 3 hours baffled by the results. ;-) Thankfully wherever I've needed to use this pattern so far, it's been on classes that would never(?) have any need for an overload, so this saves typing and readability. – underscore_d Apr 13 '16 at 14:27
  • 1
    Overloading `operator&` is not something you do lightly. For almost all types, it is simply correct to assume it will never be. `std::addressof` is mostly for generic code or exceptional situations. Using it unnecessarily will cause confusion. – filipos Apr 14 '16 at 13:01
  • 1
    @filipos Yeah, the only use-case I've seen is a proxy class, to let it return the address of the target, rather than its own. e.g. a packed bitset. This is usually done by `ProxyHost::ProxyReturn &operator[]`, so I needed `ProxyReturn::operator&` to feign the address of the target byte. Even then, I decided I was overcomplicating things and ended up not using the proxy... and _anyway_, if iterating over this, you wouldn't use an `initializer_list{&of, &pointers}` - you would use a `for` loop over `operator[]`, making this all moot. It's hard to imagine a case where one would need both of these – underscore_d Apr 14 '16 at 13:57
3

Your code: for (vector_type& vr : { var_abc, var_efg, var_xyz }) { a_func(vr); } creates an initializer_list:

An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T

So you cannot invoke a modifying function on them.

Instead use ref: for (auto& vr : { ref(var_abc), ref(var_efg), ref(var_xyz) }) { a_func(vr); }

Live Example

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
2

If you want to achieve the effect of copy pasting the function call without actual copy pasting, then use this variadic template trick:

template<typename ... Args>
void call_a_func (Args&&... args)
{
  int unused[] = { (a_func(std::forward<Args>(args)), 0) ... }; (void) unused;
}

Usage:

call_a_func(var_abc, var_efg, var_xyz, var_pqr);

I have simplified your problem in this Demo. You may extend it further. This trick is mentioned at various places in SO, I picked up from this post.

Community
  • 1
  • 1
iammilind
  • 68,093
  • 33
  • 169
  • 336
1

If you want an unfolded loop, you might be interested in for_each_arg:

template<class F, class...Ts>
F for_each_arg(F f, Ts&&...a) {
  return (void)std::initializer_list<int>{(f((Ts&&)a),0)...}, f;
}

Usage:

for_each_arg(a_func, var_abc, var_efg, var_xyz);

Of course, if you have to operate on the same set of variables repeatedly, it might be better to define an array of pointers and use iteration.

References:

https://twitter.com/ericniebler/status/559119062895431680

https://youtu.be/Za92Tz_g0zQ

https://github.com/SuperV1234/cppnow2015

filipos
  • 645
  • 6
  • 12
  • Although I can't wrap my head around its sheer hackiness, I can tell this looks like a more reusable version of @iammilind's post, as it parameterises the function too. Anyway, to be pedantic, you can't "define an array [or any other container] of references" - at most a container of `reference_wrappers`, i.e. pointers-masquerading-as-references, at which point I'd say it's best just to use pointers directly (though the `ref`-style semantics are probably invaluable in some cases). – underscore_d Apr 13 '16 at 15:26
  • 1
    @underscore_d When I wrote about array of references, I was thinking precisely about pointers. I will edit to make that clear. – filipos Apr 13 '16 at 15:31