-
-
Notifications
You must be signed in to change notification settings - Fork 10.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
It would be nice to use RAII for pushing/popping #2096
Comments
You can create C++ helpers for doing just that, they would be a few lines to implement. I am open the idea of providing an official .h file with those helpers if they are designed carefully. Begin/BeginChild are inconsistent with other API for historical reasons unfortunately :( |
I think I'll take a crack at it and post something here for people to critique. |
Something I put together a while back, only handles PushStyleVar and PushStyleColor, would probably be nice to improve it to handle other ImGui push/pop methods. Header (ImStyle_RAII.h):
Source (ImStyle_RAII.cpp):
|
My most used RAII object for Imgui:
|
Looks like there's some demand for this! Here's what I'm working with so far: The only part I can imagine being controversial is that I'm providing
|
@sethk Interesting. I have wrapped ImGui children and windows in a similar fashion but without RAII. I'm currently using lamda's for this use. ImWindowHelper and ImChildHelper implicitly calls begin/end accordingly. Ex Pseudo :
|
Consider {
ImWindowHelper(str_id, ...);
{
ImChildHelper(str_id, ...);
ImGui::Text("Child 1");
}
} which is less noisy. |
@ice1000 |
It works with clang++-6.0 but I didn't lookup the standard spec. |
Confirmed: if you don't throw exceptions in the constructor, it's destructed at the time as we expected in my code snippet above. From https://en.cppreference.com/w/cpp/language/destructor :
From http://eel.is/c++draft/class.dtor#:destructor,implicit_call :
|
@ice1000 MSVC doesn't like what your doing which suggests to me the standard isn't actually saying
Relevant ImWindowHelper class
And it compiles on MSVC v141 to this as I suspected :
Essentially it gets instantly destructed after it gets constructed, simply doing this :
Would work as expected and would be destructed where you want it to be. This is precisely why I chose lamdas, because you can definately ensure what code gets called when. |
@ice1000 That syntax reminds me of something I saw in a Facebook C++ talk about some very insidious bugs they had when RAII initializations were missing variable names and thus turned into side-effect free declarations. The compactness is nice if it works, but it loses the ability to bypass code when windows are collapsed or clipped, etc. In any case, that syntax should be possible with my wrappers, if it works for you. What C++ standard are you targetting with Clang? @maxkunes I like the idea of lambdas as well. Are there any drawbacks? I haven't used C++ lambdas much. Is there any runtime overhead when they're used as coroutines like this? |
@sethk Regarding the question about RAII you referenced @ice1000 I would direct you to my post above yours where I show that at the very least, MSVC will not work as he expects, my guess is as good as yours if it works other places. EDIT: Through more research from https://godbolt.org/ seems many compilers will call the destructor instantly after constructor when not allocated locally. Regarding lamdas, to my knowledge, they don't have many drawbacks, but I heard somewhere that std::function has a little bit of overhead (should fact check that), but for example, if you use function pointers with lamdas, I don't think there is any overhead. |
|
Just to illustrate my point, I totally forgot that https://github.com/sethk/imgui/blob/raii/misc/raii/imgui_raii.h |
@sethk Looking good! Some pieces of feedback:
|
This looks good! Another minor comment I have is: we should probably remove the copy/move constructors of those classes, even though it's unlikely someone will make the mistake of copying those guys. A simple macro can facilitate the job for that: #define IMGUI_DELETE_MOVE_COPY(Base) \
Base(Base&&) = delete; \
Base& operator=(Base&&) = delete; \
Base(const Base&) = delete; \
Base& operator=(const Base&) = delete; \ (If you want to stay pre-C++11 you can declare them private and not define them instead of deleting them.) |
I'm happy with enforcing C++11 as a requirement for those C++ extension. |
How about
Sounds good, but there is already an |
I could imagine using See this proof of concept for reference // Original 'finally' function from https://softwareengineering.stackexchange.com/a/308747
template<typename F>
[[nodiscard]] auto im_finally(F f, bool active = true) noexcept(noexcept(F(std::move(f)))) {
auto x = [f = std::move(f)](void*){ f(); };
return std::unique_ptr<void, decltype(x)>((void*)(active), std::move(x));
}
[[nodiscard]] inline auto im_window(char const *name, bool *open = nullptr, ImGuiWindowFlags flags = 0) {
return im_finally([]{ ImGui::End(); }, ImGui::Begin(name, open, flags));
}
// Usage
if (auto window = im_window("cpp_im_gui", ...)) {
// add buttons or whatever
} // ImGui::Begin == true -> unique_ptr.get() != nullptr -> call deleter
// ImGui::Begin == false -> unique_ptr.get() == nullptr -> dont call deleter
Here is a working demo of the concept https://ideone.com/KDJz25. @ocornut I would love to hear your thoughts on this approach |
@obermayrrichard Using unique_ptr like that is clever! For reference, I use the finally in Microsoft's GSL implementation - https://github.com/Microsoft/GSL/blob/master/include/gsl/gsl_util |
@sethk Apologies for later answer.
Sure.
Good question, I don't have the answer to this unfortunately. Let us think about it .. or I wonder if instead we could opt for a specific prefix to denote the type of functions we are discussing here.. @obermayrrichard tbh I find this solution really unnecessary and overkill - harder to understand, harder to debug/step into, probably slower in non-optimized builds, it raises the user C++ minimum proficiency requirements, drag unnecessary dependencies; and none of those things are in the spirit of dear imgui. I see no value in it considering the solution proposed initially is so simple and obvious to write and maintain. |
And the solution breaks down a bit if you do: auto win1 = im_window("cpp_im_gui", ...);
if(win1){
}
//win1 isn't closed at this point
auto win2 = im_window("cpp_im_gui", ...);
if(win2){
} because win1 doesn't get destroyed before win2 gets created. Also if the user doesn't create the variable it gets immediately destroyed after the condition is checked with no warning. if(im_window("cpp_im_gui", ...)){
//window is already closed
} IOW a bit too fragile in normal use for my tastes. |
But that's an issue with both RAII and the unique_ptr no? To be honest I have no idea how this
How about |
Actually it's more similar how a scope_guard works for a mutex. You need to create a local variable for the guard and make sure it goes out of scope at the correct time. With std::unique_ptr it's less of an issue because the unique_ptr itself is your handle to the resource it guards so you are not going to forget making a variable for it. |
Writing a scope-guard class and using a finally action like this is roughly equivalent, imo it's just less code and less painful to implement the individual. Both writing an RAII class and I don't see any obvious way of how we could prevent users doing |
Well if it's the same thing then why not stick to the version with no template porn? |
As I said previously
In the end it comes down to preference. I don't really care which version will be implemented since I'm not actively involved in imgui's development. |
I know I'm some years late, but how about if (RaiiWindow _("MyWindow")) // notice the _
// blah blah which at least to me looks acceptable |
The problem with
Is that if you forgot to write the "_" it will compile without warning but won't work:
That is why I prefer the macro defined scope:
It is just a syntactic sugar, generates the same code but ensures that the RAII guard is named. |
Right, that's a valid issue, but can be fixed by adding ref-qualifiers to the conversion operator. Please see https://godbolt.org/z/PeGGvqn6G. The solution you propose is really nice (in syntax and hard-to-misuse terms) but I personally prefer avoiding macros, since the rest of the functions avoid macros. |
Yes you are right, I just added ref qualifiers to my guards yesterday ;)
Yes it is a matter of user preferences, and i know that macros can be evil, but...
Internally I used other macros just to not repeat the same for every function. So i get a very small (<200 lines of code) header (including license and comments).
So yes, it is a matter of personal preferences. 👍 |
Because ImGui is written in pseudo-C, it's very easy to forget how the language you're trying to work in actually works. Unless your constraints preclude the use of more modern C++, you can entirely avoid macros and just rely on good-old C++ object lifetimes by using method-on-a-temporary https://gcc.godbolt.org/z/1xq737d1Y and for the scopes, lambdas. This is how I was able to get pure RAII scoping with https://github.com/kfsone/imguiwrap #include "imguiwrap.dear.h"
#include <array>
#include <string>
ImGuiWrapperReturnType
render_fn() // opt-in functionality that gets called in a loop.
{
bool quitting { false };
dear::Begin("Window 1") && [&quitting](){ // or make quitting a static so capture not required.
dear::Text("This is window 1");
dear::Selectable("Click me to quit", &quitting);
};
if (quitting)
return 0;
dear::Begin("Window 2", nullptr, ImGuiWindowFlags_AlwaysAutoResize) && [](){
static constexpr size_t boarddim = 3;
static std::array<std::string, boarddim * boarddim> board { "X", "O", "O", "O", "X", "O", "O", "X", " " };
dear::Table("0s and Xs", 3, ImGuiTableFlags_Borders) && [](){
for (const auto& box : board) {
ImGui::TableNextColumn();
dear::Text(box);
}
};
};
}
int main(int argc, const char* argv[])
{
return imgui_main(argv, argv, my_render_fn);
} |
Is there any information documentation or reference about this? Looking at the imgui_demo, I do not fully understand when the if (ImGui::Begin(...)) {
}
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L2479
ImGui::End();
if (ImGui::BeginChild(...)) {
}
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L3107
ImGui::EndChild();
if (ImGui::BeginTable(...)) {
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L553
ImGui::EndTable();
}
if (ImGui::MenuBar(...)) {
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L2977
ImGui::EndMenuBar();
}
if (ImGui::MainMenuBar(...)) {
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6421
ImGui::EndMainMenuBar();
} I am thinking of implementing my own RAII implementation and these two implementations matter in terms of when to apply this: class Window {
public:
Window(...) { mOpen = ImGui::BeginWindow(...); }
~Window() { ImGui::EndWindow(); }
private:
bool mOpen = false;
};
class MenuBar {
public:
MenuBar(...) { mOpen = ImGui::BeginMenuBar(...); }
~MenuBar() {
if (mOpen)
ImGui::EndMenuBar();
}
private:
bool mOpen = false;
}; |
Begin and BeginChild are the only inconsistent ones. It is all commented in imgui.h and effectively the demo.
|
Please don't implement RAII guards again and again and again.... There are many attempts already. https://github.com/mnesarco/imgui_sugar #include <imgui/imgui.h>
#include <imgui_sugar.hpp>
// ...
static int left = 0, right = 0;
ImGui::SetNextWindowPos(ImVec2(30, 50), ImGuiCond_FirstUseEver);
set_StyleColor(ImGuiCol_WindowBg, ImVec4{0.88f, 0.88f, 0.88f, 1.0f});
set_StyleColor(ImGuiCol_Text, 0xff000000);
with_Window("Test Window", nullptr, ImGuiWindowFlags_AlwaysAutoResize) {
ImGui::Text("Hello");
with_Group {
ImGui::Text("Left %d", left);
if (ImGui::Button("Incr Left"))
++left;
}
ImGui::SameLine();
with_Group {
set_StyleColor(ImGuiCol_Text, 0xffff0000);
ImGui::Text("Right %d", right);
if (ImGui::Button("Incr Right"))
++right;
with_Child("##scrolling", ImVec2(200, 80)) {
ImGui::Text("More text ...");
ImGui::Text("More text ...");
ImGui::Text("More text ...");
with_StyleColor(ImGuiCol_Text, ImVec4{ 0, 0.5f, 0, 1.0f })
ImGui::Text("More text ...");
ImGui::Text("More text ...");
with_StyleColor(ImGuiCol_Text, ImVec4{ 0.5f, 0.0f, 0, 1.0f }) {
ImGui::Text("More text ...");
ImGui::Text("More text ...");
}
}
}
ImGui::Text("Bye...");
}
// ... There are different approaches, Lambdas, Macros, ... Use one of the existent projects... |
I have created my own RAII implementation that I am really happy with. It works in the following way: if (auto table = Table("MyTable", 3)) {
// covers 95% of the use-cases for table
// for me
table.row("I am a text", glm::vec3(2.5f), 25.0f);
// Custom implementation
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Button("I want a button inside this column");
// ..other cols as well
}
if (auto _ = Window("Window title", open)) {
ImGui::Text("Inside the window");
} I may create a Macro around the API to make it cleaner but it is not important to me at the moment and it has already fixed one of the error-prone things for me, which was mismatching ends, especially when you have lots of UI code and it is easy to miss. |
In C++17 (I think), you can get C++ to generate most of the above API for you like so: namespace ImScoped {
namespace detail {
template <auto Begin, auto End, bool UnconditionalEnd = false> class Widget {
bool shown;
public:
explicit Widget (auto&&... a)
: shown{
[] (auto&&... aa) {
return Begin(std::forward<decltype(aa)>(aa)...);
} (std::forward<decltype(a)>(a)...)
} {}
~Widget () { if (UnconditionalEnd || shown) End(); }
explicit operator bool () const& { return shown; }
explicit operator bool () && = delete;
};
} // namespace detail
using Window = detail::Widget<ImGui::Begin, ImGui::End, true>;
using TabBar = detail::Widget<ImGui::BeginTabBar, ImGui::EndTabBar>;
using TabItem = detail::Widget<ImGui::BeginTabItem, ImGui::EndTabItem>;
using Table = detail::Widget<ImGui::BeginTable, ImGui::EndTable>;
// etc.
} // namespace ImScoped Deleting the rvalue-ref overload of if (ImScoped::Window(...)) // Wrong! the temporary would be destroyed immediately If you wish to add methods, you can use struct Table: detail::Widget<ImGui::Begin, ImGui::End> {
using Widget::Widget;
void row ();
// etc.
}; Regular parameter pack forwarding fails when omitting defaulted parameters, but wrapping it another time through a variadic lambda works. One downside is that, ironically, this approach fails for when the function has actual overloads, because you can't bind the non-type template parameter |
Hello, Better late than never, I would like to resume work on this and possibly merge it. I am a little bit concerned by the fact that this design requires a local variable name every-time, don't people find this to be a non-trivial annoyance to have to name something that you didn't have to name before? At the same time, I don't imagine there's a better solution. I'm afraid I don't really fancy the macro+template+using LINE solution of imgui_sugar (happy that it works for some, but I don't think this is a code style we want to merge or promote). That said, if we end up merging @sethk version of imgui_scoped.h we can always add a link to imgui_sugar on top of the file for people who may prefer this style. I am concerned that dropping the verb in some instances may be misleading. Then we have to figure out are the exact names.
if (ImScoped::Window window("My Window"))
{
ImScoped::StyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("A red hello!");
for (int n = 0; n < 10; n++)
{
ImScoped::ID id(n);
ImGui::Button("Button");
}
} I think it should be in ImGui namespace with a prefix, e.g. if (ImGui::ScopedWindow window("My Window"))
{
ImGui::ScopedStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("A red hello!");
for (int n = 0; n < 10; n++)
{
ImGui::ScopedID id(n);
ImGui::Button("Button");
}
} This seems more correct though longer. My intuition is this desire has been mostly caused by two things:
So I admit I am still puzzled why people prefer having to name things over this: if (ImGui::Begin("My Window"))
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("A red hello!");
for (int n = 0; n < 10; n++)
{
ImGui::PushID(n);
ImGui::Button("Button");
ImGui::PopID();
}
ImGui::PopStyleColor();
ImGui::End()
} BUT I FEEL i am utterly biased because I know how to not trip on the Begin/End thing. Manually edited selected bits to reflect how I think it should look: // dear imgui: RAII/scoped helpers
// see https://github.com/ocornut/imgui/issues/2096
// Usage:
/*
if (ImGui::ScopedWindow window("My Window"))
{
ImGui::ScopedStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("A red hello!");
for (int n = 0; n < 10; n++)
{
ImGui::ScopedID id(n);
ImGui::Button("Button");
}
}
*/
#pragma once
#ifndef IMGUI_DISABLE
// Move not allowed, Copy not allowed
#define IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(Base) \
Base(Base&&) = delete; \
Base &operator=(Base&&) = delete; \
Base(const Base&) = delete; \
Base& operator=(const Base&) = delete
namespace ImGui
{
// Scoped wrapper for Begin()/End()
struct ScopedWindow
{
bool IsVisible;
ScopedWindow(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0) { IsVisible = Begin(name, p_open, flags); }
~ScopedWindow() { End(); }
explicit operator bool() const { return IsVisible; }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedWindow);
};
// Scoped wrapper by PushStyleColor()/PopStyleColor()
struct ScopedStyleColor
{
ScopedStyleColor(ImGuiCol idx, ImU32 col) { PushStyleColor(idx, col); }
ScopedStyleColor(ImGuiCol idx, const ImVec4& col) { PushStyleColor(idx, col); }
~ScopedStyleColor() { PopStyleColor(); }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedStyleColor);
};
// Scoped wrapper for PushID()/PopID()
struct ScopedID
{
ScopedID(const char* str_id) { PushID(str_id); }
ScopedID(const char* str_id_begin, const char* str_id_end) { PushID(str_id_begin, str_id_end); }
ScopedID(const void* ptr_id) { PushID(ptr_id); }
ScopedID(int int_id) { PushID(int_id); }
~ScopedID() { PopID(); }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedID);
};
// Scoped wrapper for TreeNode()/TreePop()
struct ScopedTreeNode
{
bool IsOpen;
ScopedTreeNode(const char* label) { IsOpen = TreeNode(label); }
ScopedTreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(3) { va_list ap; va_start(ap, fmt); IsOpen = TreeNodeV(str_id, fmt, ap); va_end(ap); }
ScopedTreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(3) { va_list ap; va_start(ap, fmt); IsOpen = TreeNodeV(ptr_id, fmt, ap); va_end(ap); }
~ScopedTreeNode() { if (IsOpen) TreePop(); }
explicit operator bool() const { return IsOpen; }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedTreeNode);
};
...
} // namespace ImGuiScoped
#endif // #ifndef IMGUI_DISABLE If Seth or someone wants to resume that work by tweaking the Ruby script it would be nice to make it look like this? |
ImGui::ScopedStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("A red hello!"); Maybe I'm not aware of some obscure C++ rules, but I don't think that text would be red. The scoped object here is an anonymous object and should be destroyed again before the call to |
See, i exactly made the mistake i was worried about in my example… :( :) |
Hello @ocornut , I know there are many projects implementing RAII for ImGui, I also have my own . I use a single struct and some little macros to hide the local variable so naming is not a problem. It would be great to know your thoughts on my approach. |
An alternative to writing structures with RAII for everything would be to write functions which call a lambda. The sample provided by @ocornut would look something like this: ImGui::Window("My Window", [] {
ImGui::WithStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1), [] {
ImGui::Text("A red hello!");
for (int n = 0; n < 10; n++)
{
ImGui::WithID(n, [] {
ImGui::Button("Button");
});
}
});
}); |
This thread has already gone many places.
I don’t want to promote lambda (leading to debug stepping cognitive overhead + cpu overhead), and i personally don’t fancy the template/macro/__LINE__ party of imgui_sugar.
I will happily link and refer to all implementations/solutions like we already do in the wiki.
But if we have to merge something or support something officially it’s going to be closer to what the PR does. I am not even 100% sure it should be on main repo (maybe) but at least we need a maintained header that doesn’t sit in a random PR.
|
To preface, I think the lambda solution is the best fit. Anyways... Compilers should optimize out lambda usage like this. Regarding your other point of "debug stepping cognitive overhead". Can you clarify? |
Disable -O2 and look at different compilers.
We are optimizing for the worst case scenario, not only for the best case scenario.
Access to local variables may lead user to use captures which is likely to make things even more ugly on some settings.
Using a debugger is made noticeably more annoying. Extra debug steps required. Confusing names in callstacks. This is not the sorts of things dear imgui codebase is known for.
|
99.9% of time is spent debugging and I love Omar's effort to make debug
runs fast and debugging a pleasant experience in general.
…On Thu, Oct 10, 2024 at 6:31 PM omar ***@***.***> wrote:
Disable -O2 and look at different compilers.
We are optimizing for the worst case scenario, not only for the best case
scenario.
Access to local variables may lead user to use captures which is likely to
make things even more ugly on some settings.
Using a debugger is made noticeably more annoying. Extra debug steps
required. Confusing names in callstacks. This is not the sorts of things
dear imgui codebase is known for.
—
Reply to this email directly, view it on GitHub
<#2096 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFDDOTFIQ2CTQ2CUWWNXH3Z22TVDAVCNFSM6AAAAABPVE3AT2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBVGU3DMNRUHE>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Not only that, but since C++ has deprecated the usage of the catch-all |
My 2cents:
if (auto window = ImGui::Scoped::Window("My Window")) {
auto color = ImGui::Scoped::StyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
ImGui::Text("A red hello!");
for (int n = 0; n < 10; n++)
{
auto id = ImGui::Scoped::ID(n);
ImGui::Button("Button");
}
} Using the following RAII wrappers: struct Scoped;
struct [[nodiscard]] ScopedWindow
{
private:
bool IsVisible;
ScopedWindow(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0) { IsVisible = Begin(name, p_open, flags); }
~ScopedWindow() { End(); }
explicit operator bool() const { return IsVisible; }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedWindow);
friend class Scoped;
};
// Scoped wrapper by PushStyleColor()/PopStyleColor()
struct [[nodiscard]] ScopedStyleColor
{
private:
ScopedStyleColor(ImGuiCol idx, ImU32 col) { PushStyleColor(idx, col); }
ScopedStyleColor(ImGuiCol idx, const ImVec4& col) { PushStyleColor(idx, col); }
~ScopedStyleColor() { PopStyleColor(); }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedStyleColor);
friend class Scoped;
};
// Scoped wrapper for PushID()/PopID()
struct [[nodiscard]] ScopedID
{
private:
ScopedID(const char* str_id) { PushID(str_id); }
ScopedID(const char* str_id_begin, const char* str_id_end) { PushID(str_id_begin, str_id_end); }
ScopedID(const void* ptr_id) { PushID(ptr_id); }
ScopedID(int int_id) { PushID(int_id); }
~ScopedID() { PopID(); }
IMGUI_SCOPED_DISABLE_MOVE_AND_COPY(ScopedID);
friend class Scoped;
};
struct Scoped {
static ScopedId ID(int int_id) { return ScopedId(int_id); }
static ScopedId ID(const void* ptr_id) { return ScopedId(ptr_id); }
static ScopedId ID(const char* str_id) { return ScopedId(str_id); }
static ScopedId ID(const char* str_id_begin, const char* str_id_end) { return ScopedId(str_id_begin, str_id_end); }
static ScopedWindow Window(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0) { return ScopedWindow(name, p_open, flags); }
static ScopedStyleColor StyleColor(ImGuiCol idx, ImU32 col) { return ScopedStyleColor(idx, col); }
static ScopedStyleColor StyleColor(ImGuiCol idx, const ImVec4& col) { return ScopedStyleColor(idx, col); }
} |
Just wanted to add to this. To me personally the lambda solution is better than the RAII solution and can be made even more readable than what I presented if that's an issue. First, you don't have to worry about the "oops I forgot to give a name to my guard" problem, you don't have to give a name at all! Second, the code to implement such functions would be small and really simple to write, it can literally be made to just: void WithId(int n, CallableRef call) {
PushId(n);
call();
PopId();
} The same formula can be applied to all the other "scoped" calls, for every overload. Now regarding the issues presented:
This can be easily solved by capturing everything by reference, ie.
I would actually argue that it's not the case. You cannot pick the name of a lambda, but you can pick the name of a function. With this solution you can always pass a function pointer where it makes sense, such as
@nicolasnoble I don't think the committee deprecated catch-all. Can you please add a link? 🙂 PS. for reference, here's an implementation of |
Sorry, you're correct. It's |
Maybe lamba would bring lot of overhead... at least more than Basic RAII construction. |
Lambdas are almost always zero cost, producing the exact same assembly code as a simple RAII object. (Actually producing better code in the example below). They might always be zero cost (from a RAM / perf perspective) as they are a language, not library, feature. Lambda (-O2): Compiler Explorer link With no optimization, lambdas are likely more efficient as well—they generate less assembly while maintaining the same level of indirection. Lambda (no optimization): Compiler Explorer link In my opinion, assuming you use [&] captures, lambdas are objectively more ergonomic in this context. They provide language-enforced scoping, and avoid the need to name a variable purely for the sake of naming. The only reasonable argument against lambdas is that, without optimization, the call stack might include slightly less intuitive names. However, I don't think this is sufficient to dismiss a solution that is otherwise better. Additionally, for new programmers, a single example of lambda usage in this context should make its behavior immediately clear. In contrast, RAII with objects can be more confusing, as the point of destruction isn’t always obvious unless every scope is explicitly bracketed—adding extra work for the user. Edit: Fixed links to include body in both examples. |
This is especially true because some things need to be popped even when they aren't open (e.g. child regions), but it's difficult to remember that.
The text was updated successfully, but these errors were encountered: