2

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.

Renato Sanhueza
  • 534
  • 7
  • 27
  • I want to make a function that can receive any struct and print it's values in a sequentially form. – Renato Sanhueza Jun 19 '16 at 23:52
  • @RenatoSanhueza Can you elaborate on "any struct"? Does it really have to be any struct in the world? Or do you just want to be able to do this conveniently for structs that *you* are declaring without repeating logic? Or do you need to do this for arbitrary structs defined elsewhere as well? – Nir Friedman Jun 20 '16 at 00:01
  • @NirFriedman Well I want to make this function to use it on my own structs. That is why I state in the question above that the fields are only strings. The structs in which I am interested only have fields of type string. The structs can have different amount of fields between them and the name of the fields can change(obviously) because every struct represents a different thing. As you deduct I don't want to make one print function for each struct if I have a better alternative. – Renato Sanhueza Jun 20 '16 at 00:29

5 Answers5

5

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
Barrett Adair
  • 1,306
  • 10
  • 24
  • This uses the names of the fields, contrary to the requirements in the question. It also very needlessly introduces a dependency on a very heavy library, namely Boost. – Cheers and hth. - Alf Jun 20 '16 at 00:48
  • 1. It uses the names of the fields only in the BOOST_HANA_ADAPT_ADT macro invocation, which can be left right under the struct definition. Reflecting the members does not require the names. 2. Hana != Boost. You only have to use what you need in Hana. In fact, I believe this example doesn't even require Boost at all (so you can use Hana without Boost in this case). – Barrett Adair Jun 20 '16 at 01:04
  • 1
    @Cheersandhth.-Alf It's not a needless dependency really, your solution is not only UB but is also very hard to extend, the user may wake up one day and decide he wants non string fields. Some of the fields he has already don't seem to really be strings. If you want to do serious work in C++, you'll be rewriting a decent chunk of Boost anyhow, so you may as well accept it as a dependency early. – Nir Friedman Jun 20 '16 at 01:06
  • For the record, you can also use `BOOST_HANA_DEFINE_STRUCT` to define the struct and adaptation code at once, so you don't have to duplicate the names. And if you don't have access to the struct definition, you can also use `BOOST_HANA_ADAPT_STRUCT` instead of `BOOST_HANA_ADAPT_ADT`. All information [in the tutorial](http://boostorg.github.io/hana/#tutorial-introspection-adapting). – Louis Dionne Jun 20 '16 at 01:20
  • @BarrettAdair: I was not *claiming* that it is a needless dependency. I was pointing that out to you, assuming that you'd see it when it was stated. It's trivial to do this stuff without Boost even for a heterogenous struct, and of course, with the OP's constraint of all strings, it's even more trivial, so to speak. – Cheers and hth. - Alf Jun 20 '16 at 01:53
  • Regarding "Some of the fields he has already don't seem to really be strings", well, that's nonsense. You don't believe that `fruit`, `book` and `money` constitute a real collection of fields, do you? That said I agree that "stringly typed" code is an ungood idea, but that's what the OP is doing and what the *question* is about. On SO one should answer the question. In e.g. a Usenet group you can leave the question unanswered and instead discuss whether the approach in the question, is a good one in general or for this or that purpose. SO is not a discussion forum. It's a Q&A site. – Cheers and hth. - Alf Jun 20 '16 at 01:56
  • 1
    I added [an example in my answer](http://stackoverflow.com/a/37913329/464581) to show just how trivial this is without any heavy library dependency, and no clever macros. Mostly because this answer has gathered upvotes. It's like voting up the advice to use a nuclear weapon to kill a fly: yeah it works, but it's not practical. – Cheers and hth. - Alf Jun 20 '16 at 02:21
2

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.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
0

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

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • yes it's possible. it's russian level insanity, but possible: https://www.youtube.com/watch?v=UlNUNxLtBI0 – v.oddou Sep 27 '22 at 10:52
0

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

v.oddou
  • 6,476
  • 3
  • 32
  • 63
-1

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 2
    downvote: This is an incredibly bad idea, as it's just asking for some trouble down the road. Good luck debugging your code when someone decides to add another non-string member to the struct. – Louis Dionne Jun 20 '16 at 01:11
  • 1
    @LouisDionne: re “Good luck debugging your code when someone decides to add another non-string member to the struct”, it seems that you failed to understand anything here, because that does not make sense. Also, it seems you've failed to read the question. Is it your downvote? If so, please don't vote out of **ignorance**. – Cheers and hth. - Alf Jun 20 '16 at 01:45
  • I gave an upvote to all the answers that suggest a method to solve the question but I will accept this answer because it doesn't need the support of a heavy library and doesn't have hard constraint in the use of compilators. Nevertheless for the future readers that might visit this post it is worth to explore the others answers as well. I have been reading about Boost and it seems to be a very powerfull tool. Thanks to all. – Renato Sanhueza Jun 20 '16 at 02:47
  • @Cheersandhth.-Alf Your first example is *always* undefined behavior when N > 1. Pointer arithmetic is undefined behavior for pointers to objects not in an array (when the resulting offset is not exactly zero). In other words, you can't treat a struct like an array, nor its members as elements, even for standard layout types. See section § 5.7.4 in the C++ standard. Your second example is solid, though. – Barrett Adair Jun 20 '16 at 02:56
  • @BarrettAdair: You may be right about the N>1, by accident (the reasoning doesn't hold, but the conclusion may be correct). But the only reason to avoid such purely formal UB is that there's one much used compiler, namely g++, that perversely treats many a formal UB as a *carte blanche* to optimize the code to do something clearly contrary to the programmer's intention. I very much doubt the formal UB exists when there is no padding, and I furthermore doubt the g++ would do anything more serious than a sillywarning, but it's interesting as a *language lawyer* question. :) – Cheers and hth. - Alf Jun 20 '16 at 03:09
  • @Cheersandhth.-Alf The standard dictates that this is "undefined behavior". "Formal" is a weasel word. UB is UB, and it turns C++ programs into nonsense. In this case, UB exists when N > 1, *regardless of padding*. The second half of your answer is perfect, but (IMHO) the first half should look like this. Do what you want, of course. – Barrett Adair Jun 20 '16 at 03:57
  • @Cheersandhth.-Alf I see what you mean now by "out of context". I somehow missed the bit about "round-trip via casting to array of `unsigned char`. Was it added in an edit? If not, sorry for reading over it -- I originally thought your "out of context" accusation was a diversion. Indeed, I am not sure whether UB exists when a `char*` cast is used for an unpadded struct -- you may be right. – Barrett Adair Jun 20 '16 at 04:50
  • 1
    It's sad this devolved into an argument about interpretation of the standard. If it's UB, then it's a bad solution even to the compiler. If it's not UB, then it's a bad solution to the eyes of any sane software engineer. This is why I downvoted. Answering a question with something that works is easy, but actually __educating__ the poster about the state of the art is more difficult. – Louis Dionne Jun 20 '16 at 05:18
  • 2
    You The first example is UB, as several people have already pointed out. And the second example does not even meet the requirements of the question, though the poster seems unaware of it: the printing was supposed to occur without using the names of the fields. Your definition of `bag_items` uses the names of all the fields. If we're going to declare something "alongside" each struct, why not just declare a function that does the printing? – Nir Friedman Jun 20 '16 at 05:32
  • @NirFriedman: I'm the one that pointed out, in the answer, that the first example is formally UB for at least one possible case (that doesn't occur in practice). I understand why you try to give a different impression. Regarding the second example, I'm the one that pointed out, in the answer, that it uses the names. It illustrates that Barret Adair's answer completely needlessly introduces a dependency on the large Boost library to do this (for competent people) trivial thing. I think that's why he's been sowing FUD here and mustering your support in that endeavour. It's idiotic. – Cheers and hth. - Alf Jun 20 '16 at 07:02
  • @NirFriedman: Regarding your *question* "If we're going to declare something "alongside" each struct, why not just declare a function that does the printing?", why don't you ask Barret Adair that. It was he who first did that in his answer, as the only way he could think of, and with a dependency on Boost. Apparently unable to code up a simple example like I have given here, and unable to understand that it was even possible when I pointed that out. Hence his FUD here. Note: for the OP's requirements, you must ask the OP. – Cheers and hth. - Alf Jun 20 '16 at 07:09
  • Is it in some way better to use the array bag_items instead of declaring another print function? Because if it is the same in terms of writing code, efficiency and a bad practice then I should change the accepted answer. – Renato Sanhueza Jun 22 '16 at 02:16
  • @RenatoSanhueza: I can't think of any practical reason why `bag_items` would be better than just a print function, but it's close to what you asked for. The requirement to avoid using the names in at least the printing code, is yours. This answer presents the technical solutions available for that requirement. Adding a Boost dependency on top, as in the other answers so far, is decidedly worse: it buys you nothing and has a cost. But for your real world problem, not what you asked here but the real world problem, a printing function taking a stream, or a to-string function, is probably better. – Cheers and hth. - Alf Jun 22 '16 at 03:37
  • @RenatoSanhueza If the only thing you ever want to do is print, then no, there's no upside. If you however see yourself doing multiple different things, or writing multiple different print functions that print in different ways, then yes this solution is better. At least this way you are only repeating all the field names once. – Nir Friedman Jun 22 '16 at 21:52
  • @Cheersandhth.-Alf Calling a spade a spade is not a personal attack, nor is it rude. Telling people that their criticisms stem from **ignorance**, calling their behavior idiotic and their objections FUD, and saying that people are lying for misunderstandings (before your post was removed by moderator), on the other hand are all examples of rude behavior. – Nir Friedman Jun 22 '16 at 22:12
  • @NirFriedman: These additional personal attacks are, well, stupid. – Cheers and hth. - Alf Jun 22 '16 at 22:25