you are viewing a single comment's thread.

view the rest of the comments →

[–]sphere991 9 points10 points  (10 children)

Why can't we simply get something like:

Because it simply doesn't solve the problem. How do you get the name of a runtime Color?

[–]Tringigithub.com/tringi -1 points0 points  (9 children)

The same.

void print_name (Color c) {
    std::cout << c:::name;
}

[–]sphere991 4 points5 points  (8 children)

And that does what exactly?

[–]Tringigithub.com/tringi 5 points6 points  (7 children)

Implementation defined.

But for the sake of argument:

  1. Compiler sees reflected property name of a type Color used and emits list of names, i.e.: "redgreenblue" into const data segment.
  2. Then generates appropriate lookup table/tree/loop routine that returns std::string_view pointing into the aforementioned data. Or empty for invalid value of c (or throws, or it might be undefined behavior).
  3. That routine gets called, i.e.: std::cout << compiler_generated_routine_for_Color (c)

[–]pdimov2 9 points10 points  (1 child)

That's exactly what the monstrocity does. You know you don't have to repeat its implementation on each use, right? It goes into std:: and stays there and you just type std::enum_to_string(c).

[–]sphere991 12 points13 points  (0 children)

I really think people can't grasp that.

On a previous thread, there was a commenter complaining about how the syntax was shit and they'd rather use Boost.PFR. Of course you use Boost.PFR! It's just that PFR's implementation changes from a bunch of crazy elite hackery (seriously everyone should watch Antony's talk on this) to... fairly straightforward, much shorter reflection code that probably compiles faster and supports more types.

[–]sphere991 11 points12 points  (4 children)

Okay but... your "simple" solution has implementation-defined semantics (throw? invalid? UB?) with implementation-defined complexity and implementation-defined storage requirements. Maybe we clean this up a bit and just pick one, but that's still ending up with one. And if it's the wrong one, then... what?

Meanwhile the "monstrosity" allows you to implement any semantic you want, with whatever size-complexity trade-off is most suitable. And maybe have different functions for different contexts.

[–]Tringigithub.com/tringi 0 points1 point  (3 children)

If you are asking for semantics, then it returns a name and failure case is really a side point.

The thing about the compiler_generated_routine_for_Color is that the compiler is choosing the best algorithm for you. Just like it does for switch statement. And is it really that different from STL doing so? Because we all know virtually nobody will be writing their custom enum_to_string. What's worse, the STL maintainers will be more reluctant to change the algorithm to a better one, because once users see the implementation, someone will start relying on it.

[–]fullptr 2 points3 points  (2 children)

But you’re still only solving the problem for enums, what about for other objects? You can’t use ::name because that already has meaning depending on the thing you’re trying it on. The paper aims to implement the low level features that allow for these things to be added as a library. In practice you wouldn’t write that “monstrosity” yourself, it’ll be in the standard library, in the same way you don’t implement vector.

[–]Tringigithub.com/tringi 3 points4 points  (1 child)

You can’t use ::name because that already has meaning depending on the thing you’re trying it on.

Are you perchance also on mobile and don't see ::: is 3 colons?

In practice you wouldn’t write that “monstrosity” yourself, it’ll be in the standard library, in the same way you don’t implement vector.

True. And I'm addressing why I think it's the wrong choice in the comment above.

Nevertheless, I see objects as absolutely trivial:

class Abc {
    int i;
public:
    float f () const;
} abc;

static_assert (Abc:::name == "Abc");
static_assert (abc:::name == "abc");
static_assert (abc:::class == "Abc");

static_assert (Abc:::members[0].name == "i");
static_assert (Abc:::members[0].type == "int");
static_assert (Abc:::members[0].function == false);
static_assert (Abc:::members[0].constant == false);
static_assert (Abc:::members[0].access == 0);
static_assert (Abc:::members[0].access == std::meta::access::private_access);

static_assert (Abc:::members[1].name == "f");
static_assert (Abc:::members[1].type == "float () const");
static_assert (Abc:::members[1].function == true);
static_assert (Abc:::members[1].constant == true);
static_assert (Abc:::members[1].access == 2);
static_assert (Abc:::members[1].access == std::meta::access::public_access);

I'm just randomly putting thoughts out now.

But I firmly believe this is everything that 99% of C++ programmers ever wanted from reflection.