Entt: Empty struct as component

Created on 5 Oct 2019  路  18Comments  路  Source: skypjack/entt

Couldn't find an issue about this, and I can't quite figure out what's happening.

Why does this work..

static entt::registry World;
struct Renderable { bool tag; };

int main() {
  World.view<Renderable>().each([=](auto& renderable) {
     // Do things
  });
}

But not this..

static entt::registry World;
struct Renderable {};  // Empty struct!

int main() {
  World.view<Renderable>().each([=](auto& renderable) {
     // Do things
  });
}

Note the empty Renderable {}.

Error

$ g++ -pthread -std=c++17 -o main main.cpp
$ chmod +x main
$ ./main

entt.h: In instantiation of 'entt::basic_view<Entity, Component>::each(Func) const [with Func = main()::<lambda(auto:80&)>; Entity = entt::entity; Component = Renderable]::<lambda(auto:23)> mutable [with auto:23 = entt::entity]':
/usr/local/include/c++/8.1.0/bits/stl_algo.h:3882:5:   required from '_Funct std::for_each(_IIter, _IIter, _Funct) [with _IIter = entt::sparse_set<entt::entity>::iterator; _Funct = entt::basic_view<Entity, Component>::each(Func) const [with Func = main()::<lambda(auto:80&)>; Entity = entt::entity; Component = Renderable]::<lambda(auto:23)>]'
entt.h:7150:30:   required from 'void entt::basic_view<Entity, Component>::each(Func) const [with Func = main()::<lambda(auto:80&)>; Entity = entt::entity; Component = Renderable]'
main.cpp:9:4:   required from here
entt.h:7151:25: error: no match for call to '(main()::<lambda(auto:80&)>) (const entt::entity&, entt::basic_storage<entt::entity, Renderable, void>::iterator::reference)'
                     func(entt, *(raw++));
                     ~~~~^~~~~~~~~~~~~~~~
main.cpp:7:53: note: candidate: 'template<class auto:80> main()::<lambda(auto:80&)>'
   World.view<Renderable>().each([=](auto& renderable) {
                                                     ^
main.cpp:7:53: note:   template argument deduction/substitution failed:
In file included from main.cpp:1:
entt.h:7151:25: note:   candidate expects 1 argument, 2 provided
                     func(entt, *(raw++));
                     ~~~~^~~~~~~~~~~~~~~~
question

Most helpful comment

[*] start _fair enough_ mode [OK]

Honestly, 99% of the times you don't want to disable the empty type optimization and surely that's not something I want in my own code. Similarly, I don't want to define the ENTT_ENABLE_ETO all around, especially in already existing code or already released projects where suddenly empty components would be instantiated.
That's more or less why it's an opt-in solution rather than an opt-out macro.

[*] stop _fair enough_ mode [OK]

All 18 comments

You miss entity as first parameters of your lambda

()

No @Milerius entity is optional: if you don't need it, don't require it, it's faster.

At a first glance, the problem is auto &. Empty types aren't created to save space in the pool, so temporaries are returned. It should be either auto or const auto & or auto &&.
Otherwise use less instead of each. It doesn't return empty components and thus it's slightly faster. Take a look at the doc for the details. :)

Thanks guys.

Would it make sense to make this information available in the error message? At least in a debug build. Right now, it's a bit cryptic IMO.

@alanjfs that's a deduction error, I don't really know how to _prevent_ it and return a meaningful message. Probably the best we can do is to test if the function is invocable and notify the user if it's not, but we cannot easily tell the reason without actually invoking it.

Do you have any suggestion?

Hm, not sure. But if it's about special treatment of empty structs in favour of performance, then perhaps it could make sense to have a flag of sorts?

  1. I want great error messages
  2. I want great performance

E.g.

#define ENTT_NICE_ERRORS true
#include "entt.hpp"
...

Not sure. From a user perspective, I would have liked something to save the time it took to encounter, narrow down and converse about the problem. But at the very least there's now an issue about it users are able to search for next time it happens.

For EnTT v4 (C++20) you could use [[no_unique_address]] to maximise performance and maintain API consistency.

Well @alanjfs the thing is documented afaik and you're right in saying that there is now an issue for newcomers. Unfortunately, the nice errors flag has the same problem I mentioned above: I've no idea about how to intercept this situation. The only thing we can do is to instantiate empty components to get rid of the error but it ruins a bit the performance, so it doesn't worth it imho.

Cool, no problem on my end. Welcome to close this.

Ran into this again, in a different spot, with a different (equally cryptic) error message. :( From a user perspective, I would happily trade consistency and stability over performance. Maybe there's a way of making this work, whether a struct is empty or not, with an option to make it run "fast" afterwards?

struct Empty {};
void Func(entt::entity ent, entt:registry& reg, Empty& comp) {}
registry.on_construct<Empty>().connect<&Func>();
  c:\project\entt\entt\signal\delegate.hpp(127,28): error C2607: 
 static assertion failed (compiling source file src\main.cpp) [c:\project 
\application\application.vcxproj]                                                                         
  c:\project\entt\entt\signal\delegate.hpp(133,29): error C2672: 
 'std::invoke': no matching overloaded function found (compiling source file src\main.cpp) [C:\Users\marc 
us\Dropbox\dev\toy\vs\cargame\application\application.vcxproj]                                            
  c:\project\entt\entt\signal\delegate.hpp(126,1): error C2893:  
Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<exp 
r>)' (compiling source file src\main.cpp) [c:\project\application\applica 
tion.vcxproj]

A bit out of scope but do you know that you can attach listeners with a list of parameters that is shorter than that of the signal?

In your case, these are valid as well:

void聽Func(entt::entity聽ent,聽entt:registry&聽reg)聽{}
void聽Func(entt::entity聽ent)聽{}
void聽Func()聽{}

If you don't need the component (and you don't need it because it's empty probably) just don't get it. :wink:

do you know that you can attach listeners with a list of parameters that is shorter than that of the signal?

I did not! Thanks :) The docs could use some refinement on this.

Actually, to paint a better picture on this; I do need the struct, just not yet. I'm working iteratively, and wanted to get a placeholder struct to hook things up before I started filling things in. The idea was that once I got EnTT to play nice, I'd add data and logic to those parts. In this case, I never got that far, because this connection didn't work for some unknown reason.

I've thought about this though.
I think it's not that hard to disable the empty type optimization by means of a macro and therefore to treat all the types as if they were non-empty ones.

Feel free to open an issue for that. I'll look into it further.
This fits your idea of having consistency initially and enabling optimizations in release mode only if needed.

Nice one!

Feel free to open an issue for that. I'll look into it further.

Maybe we could re-open this one? That way, we get to keep the conversation around the issue intact.

It makes sense actually. :+1:

Thanks for addressing this, however! This doesn't actually fix the issue IMO.

The problem was getting this error and not understanding why, because of a cryptic error message. The solution added doesn't solve this, because it is opt-in; which means past-me would still run into this issue. Like before, I could have solved it if I knew about it, by e.g. adding const to the each() function, but I didn't know. Likewise, with this flag, past-me and others like me will continue to run into this issue.

Instead, I would suggest making the solution opt-out; as in, have a flag to explicitly enable the optimisation. That way:

  1. Beginners won't have a problem, but will "suffer" from poor performance (except they will have bigger problems, such as learning the library)
  2. Those wanting more performance (once familiar and committed) can learn about and use the flag.

Win-win.

For clarity, here's what I would suggest.

// #define ENTT_DISABLE_ETO // Before
#define ENTT_ENABLE_ETO     // After

[*] start _fair enough_ mode [OK]

Honestly, 99% of the times you don't want to disable the empty type optimization and surely that's not something I want in my own code. Similarly, I don't want to define the ENTT_ENABLE_ETO all around, especially in already existing code or already released projects where suddenly empty components would be instantiated.
That's more or less why it's an opt-in solution rather than an opt-out macro.

[*] stop _fair enough_ mode [OK]

Was this page helpful?
0 / 5 - 0 ratings