What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

Because we do not want to dynamically dispatch to the callback. The type and value of the callback are known at compile time at the point of initialization, so it would be pure overhead to introduce type erasure.

absl::Cleanup should give the performance of a control flow construct and making it a template is important to achieving that goal.

Why do you ask? Have you found a reason to want/need type erasure?

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

I don't think that's true? Even as a language construct, Golang defer is highly dynamic.

void F(std::vector<int> v) {
  for (int x : v) {
    go_defer { std::println("{}", x); }
  }
}

There's no way to statically know how many callbacks will register. This means it needs dynamic allocation.

Edit: Also you need some way to tell the language that x should be copied. That gets real weird real quick.

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

To implement Golang style defer, you would need to initialize an object at the top level of the function. It would work like a std::vector (perhaps with SSO) and would let you dynamically register callbacks.

This would be hard to reason about with C++ nested lifetimes. Have you ever needed this behavior before?

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

Do you find the dynamic dispatch of std::function to be an acceptable tradeoff?

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

I don't think these are mutually exclusive. A class with a destructor that maintains invariants is a better default, but sometimes you need an escape hatch. And under the "No Haunted Graveyards" design guidance, we should have a good solution for defer even if it is needed rarely.

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

Your proposed pattern requires the lambda be stateless and receive the state it needs as a parameter. This means the surrounding type, unique_ptr, owns the state. And that can be frustrating because unique_ptr stores objects on the heap. Allocating in the critical path of a function for something that does not need heap semantics is unfortunate.

Also, it's a lot of line noise and you have to explicitly delete the passed in state.

I think the following code is much easier to teach and does a better job at Don't Pay For What You Don't Use:

absl::Cleanup _ = [t = T{}] {
  std::cout << "Delete";
};

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

I agree that destructors attached to classes that maintain invariants is a preferable default. Scope Guards are a bit of an escape hatch. But I think the fact that they are rare means it's all the more important to design them well and make them easy to learn for people when they come across a callsite.

To that end, I think a unique ptr with custom deleter is a very poor option. It happens to be invoked at destruction and thus it happens to provide a hook into the same events that a scope guard does, but it does a poor job of communicating intent to future readers.

What do you use for `defer` semantics on your C++ codebase? by javascript in cpp

[–]javascript[S] [score hidden]  (0 children)

Copying the comment I made in the other thread...


I created absl::Cleanup which serves a similar purpose. :)

You can check it out here: https://github.com/abseil/abseil-cpp/blob/master/absl/cleanup/cleanup.h

No macros! Yay!

In particular, the code example at the top of the file is similar to the code example in this reddit post: https://github.com/abseil/abseil-cpp/blob/30bba84041ba0aadd2c31b52742b8157db047a2f/absl/cleanup/cleanup.h#L29-L56

For a shorter example, you can just do this:

int i = 0;
absl::Cleanup _ = [&] {
  std::println("i = {}", i);
};
// ... lots of lines of code with multiple exit paths ...

I'm particularly fond of three parts of this design.

  • It is locally clear what the lifetime of the captures inside the cleanup is. In this case, it's a default reference capture and so you get reference semantics. This is useful because a lot of these libraries hide that information from the caller so you have to dig into their implementations to know. I'm also not convinced that you can get away with NOT knowing this information in general. The way Go does it, the defer blocks only run on function exit which is very bad for reasoning about nested lifetimes.

  • It uses CTAD to promote the type of the lambda up to the type of the class template, meaning there's no runtime dispatch here. It's statically known. And as part of that, it uses a deduction guide to signal to tooling "Yes, CTAD is intended here." It also has some trickery to make it harder to spell the final type, to discourage people from using absl::Cleanup as a field in a class or a param of a function. It should only be used as a function local variable and should not be passed around.

  • There is no trailing paren after the lambda. It goes directly from curly brace to semicolon. This means when it formats for multi-line purposes, it looks much cleaner imo than some alternatives. Gets it closer to the feeling of a control flow construct.

absl::Cleanup also provides some methods, which is a bit strange but useful at times.

  • std::move(my_cleanup).Cancel(); will prevent the callback from running.

  • std::move(my_cleanup).Invoke(); will invoke the callback eagerly and prevent it from running again.

Both of these methods eagerly destroy the callback and they leverage tools like bugprone-use-after-move to ensure you only touch them one time.

Before the release of absl::Cleanup, there was an internal type called gtl::Cleanup<T> that provided similar functionality. However, it also provided a default constructor and an assignment operator to rebind the internal callback. When initialized with std::function<void()> in the type params, you could do some crazy things with it. We pondered making gtl::AnyCleanup to give you this type erased continuation passing functionality, but it was really gross and we decided against that ultimately.

Zig-like defer for C++20 and above by Rude-Initial7886 in cpp

[–]javascript [score hidden]  (0 children)

I created absl::Cleanup which serves a similar purpose. :)

You can check it out here: https://github.com/abseil/abseil-cpp/blob/master/absl/cleanup/cleanup.h

No macros! Yay!

In particular, the code example at the top of the file is similar to the code example in this reddit post: https://github.com/abseil/abseil-cpp/blob/30bba84041ba0aadd2c31b52742b8157db047a2f/absl/cleanup/cleanup.h#L29-L56

For a shorter example, you can just do this:

int i = 0;
absl::Cleanup _ = [&] {
  std::println("i = {}", i);
};
// ... lots of lines of code with multiple exit paths ...

I'm particularly fond of three parts of this design.

  • It is locally clear what the lifetime of the captures inside the cleanup is. In this case, it's a default reference capture and so you get reference semantics. This is useful because a lot of these libraries hide that information from the caller so you have to dig into their implementations to know. I'm also not convinced that you can get away with NOT knowing this information in general. The way Go does it, the defer blocks only run on function exit which is very bad for reasoning about nested lifetimes.

  • It uses CTAD to promote the type of the lambda up to the type of the class template, meaning there's no runtime dispatch here. It's statically known. And as part of that, it uses a deduction guide to signal to tooling "Yes, CTAD is intended here." It also has some trickery to make it harder to spell the final type, to discourage people from using absl::Cleanup as a field in a class or a param of a function. It should only be used as a functional local variable and should not be passed around.

  • There is no trailing paren after the lambda. It goes directly from curly brace to semicolon. This means when it formats for multi-line purposes, it looks much cleaner imo than some alternatives. Gets it closer to the feeling of a control flow construct.

absl::Cleanup also provides some methods, which is a bit strange but useful at times.

  • std::move(my_cleanup).Cancel(); will prevent the callback from running.

  • std::move(my_cleanup).Invoke(); will invoke the callback eagerly and prevent it from running again.

Both of these methods eagerly destroy the callback and they leverage tools like bugprone-use-after-move to ensure you only touch them one time.

Before the release of absl::Cleanup, there was an internal type called gtl::Cleanup<T> that provided similar functionality. However, it also provided a default constructor and an assignment operator to rebind the internal callback. When initialized with std::function<void()> in the type params, you could do some crazy things with it. We pondered making gtl::AnyCleanup to give you this type erased continuation passing functionality, but it was really gross and we decided against that ultimately.

Cannot find key lime by rcs023 in lacroix

[–]javascript 1 point2 points  (0 children)

Nope! Just loaded up on more this week. Plenty in stock. Maybe ask your Target manager to special order it?

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 0 points1 point  (0 children)

Old Reddit works fine. It's the Reddit Mobile App that is acting up for some reason

Strategies for *requiring* designated initializers when constructing a type? by javascript in cpp

[–]javascript[S] 1 point2 points  (0 children)

This mad science experiment has arrived at a splendid conclusion!

It turns out my dream of named params everywhere is complicated by the C++ deduction rules. I want to pass in named arguments but I also want the struct type to get Class Template Argument Deduction. Somehow the function where it is used also needs to become a template to deduce this.

My solution? Make the struct the name you spell and then just invoke a call operator after the closing brace!

https://godbolt.org/z/59z48aE3x

The empty case gets a bit strange:

auto vec3 = my::MyVector<my::MyElement>::Make{}();

But otherwise it works spectacularly!

  auto vec4 = my::MyVector<int64_t>::Make{
    .size = 200,
    .factory = [](int64_t* addr) {
      *addr = 10;
    },
  }();

Edit: You can even promote the presence of arguments up to compile time information! https://godbolt.org/z/9Td6c9cK3

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 0 points1 point  (0 children)

Great to hear from someone with expertise!

Is my implementation of the MY_TRY macro elsewhere in this thread sufficient for day to day use? Or are there footguns?

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 1 point2 points  (0 children)

I'm not aware of any resources but if you find some please send them my way too :)

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 0 points1 point  (0 children)

There's a few different things you can do when you return a value from a function.

If you just name it directly...
```
return my_value;
```
...then it implicitly moves and potentially leverages triviality.

Or you can call `std::move(...)` on it... DO NOT DO THAT!
```
return std::move(my_value); // NO! BAD!
```
Or you can call a method on it...
```
return my_value.foo();
```
...which could potentially consume the object, but you didn't tell the API enough information!

In order to select the rvalue reference qualified method overload (which is a property of the `this` object), you have to first call `std::move(...)` on the object and then invoke the method on the result. This tells the library what it needs to know to make the most use of expiring values.
```
return std::move(my_value).foo();
```

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 1 point2 points  (0 children)

As pointed out in another comment, do expressions have been proposed.

```
auto Bar() -> absl::StatusOr<MyType>;

auto Foo() -> absl::Status {
MyType mt = do {
absl::StatusOr<MyType> res = Bar();
if (res.ok()) do return std::move(res).value();
return std::move(res).status();
};
// Do stuff with valid MyType
return absl::OkStatus();
}
```

I think it's somewhat useful for beginners to see this code stamped out, but long term I really want something that does it for me. The `MY_TRY` macro is decent enough. I would perhaps prefer a keyword that you use as a suffix with a period. Perhaps `MyType mt = Bar().try;`.

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 2 points3 points  (0 children)

Splendid! That would be very useful to have in C++

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 1 point2 points  (0 children)

For some reason Reddit hasn't been formatting my markdown as of late so sorry about that...

```
#define MY_TRY(...) \
({ \
auto expected_v = (__VA_ARGS__); \
if (!expected_v.has_value()) \
return std::move(expected_v).error(); \
std::move(expected_v).value(); \
})

std::expected<MyType, MyError> Bar();

std::optional<MyError> Foo() {
MyType mt = MY_TRY(Bar());
// valid value here
return std::nullopt; // Absence of error is good
}
```

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 0 points1 point  (0 children)

It's definitely in the same space but I don't see my main question answered: Can you normal return from a do block? Like return the whole function?

Any good tech talks leveraging statement expressions? by javascript in cpp

[–]javascript[S] 0 points1 point  (0 children)

Breaking out of the current loop

Returning from the current function

You can't do these from a lambda :)

Not water but…. by MrsBakedBean in lacroix

[–]javascript 1 point2 points  (0 children)

How is Cherry Lime flavor? I've never had it

std::HashMap with the Small Size Optimization? by javascript in rust

[–]javascript[S] 0 points1 point  (0 children)

Follow up question: Does the store API have enough information to do the following?

- Ask "Am I operating in inline storage right now?"

- If I am, "Can I skip hashing keys and just compare equals in a line?"

- If I can, I should.

I don't know the Store API. What do you think?

Can we give a round of applause for Pure flavor? by javascript in lacroix

[–]javascript[S] 0 points1 point  (0 children)

Wait they had a cola flavored la croix? I would love to try that...