7

If I have a class object comprised of 3 doubles; x, y and z, is there a way to loop over them in a function?

e.g.

for(i in (x,y,z))
    do something;

I can do something similar with explicit casting, but I am wondering if there is a more elegant solution to this.

class testc {
public:
    double x, y, z;

    testc(double x, double y, double z)
        :x(x), y(y), z(z)
    {}
};

int main()
{
    testc omega(1, 2, 3);
    cout << *(double*)&omega << " " << *((double*)&omega +1) << " " << *((double*)&omega +2);
}
  • Do your members always have the same type? – HolyBlackCat Jun 11 '20 at 15:35
  • I *think* `*((double*)&omega +1)` (you forgot the parenthesis) is a strict aliasing violation and UB, and it needs to be `*(double *)((char*)&omega + sizeof(double))`. – HolyBlackCat Jun 11 '20 at 15:36
  • Don't do that even if it's possible. Re-design your class. – Yves Jun 11 '20 at 15:37
  • 2
    Technically, you could have `for (auto el : { omega.x, omega.y, omega.z }) {// your code}`. It is probably better to use a vector or an array container instead. – Ron Jun 11 '20 at 15:39
  • 1
    Sorry to see this one closed, I was just writing an answer using range-based for loop initialization statements (C++20) allowing us to used structured bindings as the init-statement: `for(auto [x, y, z] = omega; auto e : {x, y, z}) { std::cout << e << " "; }` (give or take some `auto` constness). – dfrib Jun 11 '20 at 15:50
  • 1
    @dfri: I've reopened it. My answer is weak. Put in a placeholder to keep the dupe police at bay! – Bathsheba Jun 11 '20 at 15:50
  • @Bathsheba thanks! (My answer doesn't really apply to the target as it seems to want to inspect an arbitrary number of unknowingly named members). – dfrib Jun 11 '20 at 15:52
  • @Acorn `std::is_standard_layout_v` says it's standard-layout. – HolyBlackCat Jun 11 '20 at 15:54
  • @HolyBlackCat Yeah, never mind, I thought it had a private data member too. – Acorn Jun 11 '20 at 17:06

2 Answers2

6

(C++20) Looping over a known number of public members: structured bindings in a range-based for loop initialization statement

As of C++20 we may combine structured bindings with range-based for loop initialization statements (the latter is a C++20 feature):

Grammar

for ( init-statement(optional) range-declaration : range-expression ) ...

Specifically, using structured bindings as the init-statement in a range-based for loop:

#include <iostream>

class Foo {
public:
    double x, y, z;

    Foo(double x, double y, double z) : x(x), y(y), z(z) {}
};

int main() {
    const Foo foo(1., 2., 3.);

    for (auto [x, y, z] = foo; auto e : {x, y, z}) {
        std::cout << e << " ";
    } // 1 2 3

    return 0;
}

Note, however, that you can only use structured bindings to decompose public members of your class (in your example all members are public). Moreover, for the initializer list in the range-expression of the range based for loop, you may not have conflicting types, meaning this approach is limited to the context of your example: where all public members are of the same type.

Why use the structured bindings instead of just list-initializing the class members directly?

Now, if the (public) members of your class as well as the instance of it all have very brief names, we may want to consider omitting the structured bindings and instead list-initialize the class members directly:

const Foo f(1., 2., 3.);
for (auto e : {f.x, f.y, f.z}) {
    std::cout << e << " ";
} // 1 2 3

However, albeit arguably briefer, the drawback is that we no longer get any help from the compiler in spotting whether we've actually exactly decomposed all public members (no less, no more) or not, a check that is present when we use structured bindings:

for (auto [x, y] = foo; auto e : {x, y}) { /* ... */ }
// error: type 'Foo' decomposes into 3 elements, 
//        but only 2 names were provided

for (auto [x, y, z, xx] = foo; auto e : {x, y, z, xx}) { /* ... */ }
// error: type 'Foo' decomposes into 3 elements, 
//        but 4 names were provided
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • That's posh. Where does `omega` come into it? – Bathsheba Jun 11 '20 at 15:55
  • @Bathsheba Ah sorry, I simply referred to the OP:s own example, but it's better if this is standalone, thanks. – dfrib Jun 11 '20 at 15:57
  • Due to the fact this question was temporary closed as duplicate, I proposed the same solution here: https://stackoverflow.com/questions/19059157/iterate-through-struct-and-class-members/62329191#62329191. However I'm interested if this approach can be extended to arbitrary number of elements. – Dmitry Kuzminov Jun 11 '20 at 16:41
1
for (auto&& i : std::vector<double>{x, y, z}){
    // Do something, `i` is the double.
}

is one way, at the expense of creating a vector. I'm not convinced a compiler would detect this as an idiom and make optimisations either.

The behaviour of code like *(&x + 1) in order to attempt to reach y is undefined. (Pointer arithmetic is only valid within arrays.)

Can't you use a std::array or a std::vector for x, y, and z?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • "The behaviour of code like *(&x + 1) in order to attempt to reach y is undefined. (Pointer arithmetic is only valid within arrays.)" Works fine on my end. I dont want to declare a vector since Im looking for maximum efficiency. I dont want to use an array since I want to access data members of an object in a natural way; obj.x, obj.y etc – Mare Sorin-Alexandru Jun 11 '20 at 15:52
  • 4
    @Skcoica: One manifestation of undefined behaviour is code appearing to work. But it's still UB and a good compiler will warn. – Bathsheba Jun 11 '20 at 15:53
  • Can you give an example of a good compiler then ? Im using Visual Studio with default settings. – Mare Sorin-Alexandru Jun 11 '20 at 15:58
  • 1
    @Bathsheba Would `*(double*)((char*)&x + sizeof x)` be well-defined, or also UB? – HolyBlackCat Jun 11 '20 at 15:58
  • 1
    @Skcoica You could check the documentation for how to turn on more (or any?) warnings during compilation. – underscore_d Jun 11 '20 at 16:05
  • @HolyBlackCat: UB alas. In C you could put a `char[]` and the `double`s in a `union` and type-pun. – Bathsheba Jun 11 '20 at 16:07
  • 1
    @Skcoica: MSVC is extremely permissive. And given the implementation of COM relies on such things, it's unlikely the MSVC devs would add warnings for this sort of stuff. – Bathsheba Jun 11 '20 at 16:08
  • 1
    Why using `std::vector` instead of (`std::`)`initializer_list`? `for (auto&& i : {x, y, z})`? – Jarod42 Jun 11 '20 at 16:28
  • *"UB alas"* Hmm. I've asked a [proper question](https://stackoverflow.com/questions/62329008/is-it-ub-to-access-a-member-by-casting-an-object-pointer-to-char-adding-an) about this. – HolyBlackCat Jun 11 '20 at 16:29
  • 1
    @HolyBlackCat: Ooh - proper question. That's posh. Nice plug. I'll upvote it. – Bathsheba Jun 11 '20 at 16:32