Renato Sanhueza
Renato Sanhueza

Reputation: 564

Printing the values of an instantiated structure without using the names of the fields in C++

Can I do it?

For example consider the following structure:

struct bag {
     string fruit;
     string book;
     string money;
};

I want to print the values of the fields of an instance of the structure bag in a sequentially form and obtain an output like this:

apple
Computer Networking, A top-down Approach
100

But without using the names of the fields(fruit, book and money). Any help would be appreciated. The only information I know it is that all the fields are C++ strings.

Upvotes: 2

Views: 1551

Answers (5)

v.oddou
v.oddou

Reputation: 6775

May I suggest: boost/pfr

It will work with any aggregate type in C++17 (not limited to only strings)
And it has the good taste of not depend on the whole boost, so you can just copy the pfr subfolder.

more intel: https://github.com/boostorg/pfr

code:

#include <boost/pfr.hpp>
#include <string>
#include <iostream>

using std::string;

struct bag {
     string fruit;
     string book;
     string money;
};

int main()
{
    bag b {"apple", "Computer Networking", "100"};
    std::cout << boost::pfr::io(b);
}

output:

Program returned: 0
    {"apple", "Computer Networking", "100"}

running example: https://godbolt.org/z/haczMKaj4

conference from Antony Polukhin on the inner workings of pfr: https://www.youtube.com/watch?v=UlNUNxLtBI0

Upvotes: 0

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145369

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.

Upvotes: -1

Barrett Adair
Barrett Adair

Reputation: 1316

Although C++ does not have reflection, you can make your own reflection tools with Boost.Hana. Here is a complete program that iterates over the members of your struct, printing their names and values.

Hana requires a modern, C++14-compliant compiler, which means recent versions of Clang or GCC 6+ are your only options right now for this code.

Edit: This code now uses BOOST_HANA_ADAPT_STRUCT instead of BOOST_HANA_ADAPT_ADT.

#include <boost/hana/adapt_struct.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/fuse.hpp>
#include <string>
#include <iostream>

namespace hana = boost::hana;

using std::string;

struct bag {
    string fruit;
    string book;
    string money;
};

BOOST_HANA_ADAPT_STRUCT(bag, fruit, book, money);

int main() {

    bag my_bag{ "Apple", "To Kill A Mockingbird", "100 doubloons" };

    hana::for_each(my_bag, hana::fuse([](auto member, auto value) {
        std::cout << hana::to<char const*>(member) << " = " << value << "\n";
    }));
}

Output:

fruit = Apple
book = To Kill A Mockingbird
money = 100 doubloons

Upvotes: 5

Nir Friedman
Nir Friedman

Reputation: 17704

If you only want to use this on structs you define yourself, you can do this using Boost Fusion. You would declare your struct like this:

BOOST_FUSION_DEFINE_STRUCT(
(your_namespace1)(your_namespace2),
    bag,
        (string, fruit),
        (string, book),
        (string, money)
)

It's a little annoying to use an ugly macro, but at the end of the day it results in the same struct being generated, plus a whole lot of other stuff, and there's no repetition at least.

Once you've declared your struct this way, it's now considered a Boost Fusion sequence, and you can operate on it with various tools from Boost Fusion, including for_each which allows you to apply a generic function to each element in your struct in turn.

#include <boost/fusion/include/for_each.hpp>

bag b1;
// populate bag
boost::fusion::for_each(b1, [] (const auto& x) { std::cout << x << "\n";});

This will print out each of the fields on your line. You can do all kinds of things quite nicely this way. As a bonus this will work for non string fields without any extra work, so you could for instance make money a double instead (which perhaps makes more sense, though I couldn't tell from the question).

Edit: I see that barret and I posted simultaneously. Hana is a newer, more modern library that supersets the functionality (I believe) of Boost Fusion. If you can use Hana, by all means go ahead, but some of its requirements for compilation are harsh, especially if you want to target gcc, most of the world is still on the 5 series. And compiling with many other compilers is also not currently possible, afaik icc and msvc don't work at all. For a straightforward problem like this, you can solve it just fine with Fusion, and honestly today that is what I would recommend. A year from now hopefully my recommendation would be different.

Upvotes: 2

Sam Varshavchik
Sam Varshavchik

Reputation: 118445

Based on your clarification in the comments: this is not possible in C++. C++ does not have reflection. C++ does not work this way.

Upvotes: 0

Related Questions