Since you know that all fields are std::string
, and since you presumably control the aggregate class, you can in practice simply treat it as a raw array of std::string
:
Bag x;
static constexpr int n = sizeof(x)/sizeof(std::string);
for( int i = 0; i < n; ++i )
{
cout << reinterpret_cast<std::string const*>( &x )[i] << "\n";
}
If you know how many fields, then you can assert statically on the total size of the class, and thus guarantee that the compiler has not added any padding (it's allowed to add padding just about anywhere except before the first field, which must be at offset 0):
#define STATIC_ASSERT( e ) static_assert( e, #e " // <- must be true" )
STATIC_ASSERT( sizeof( Bag ) == 3*sizeof( std::string ) );
Otherwise treating it as an array is formally Undefined Behavior, but I don't think that one can find a compiler that by default will add any padding, so it should work very portably in practice.
Disclaimer for the above: code not reviewed by a compiler.
If you want to really be on the safe side, not even pedantic-formal UB, and if that outweighs the requirement to not at all use the item names, then simply declare the necessary knowledge for iteration, alongside each struct, e.g.:
#include <array>
#include <string>
using namespace std;
struct Bag
{
string fruit;
string book;
string money;
};
array<string Bag::*, 3> const bag_items = {&Bag::fruit, &Bag::book, &Bag::money};
#include <iostream>
auto main()
-> int
{
Bag const bag = { "Apple", "Zen and TAOMM", "billion bucks" };
for( auto mp : bag_items )
{
cout << bag.*mp << "\n";
}
}
For a heterogenous struct (items of different types) you'd more naturally use functions than direct member pointers.
But as this shows, there's no need to add in a dependency on a large library like Boost even when one is totally scared of formal UB: it's trivial to do this.