07:48 <+bridge> <33370928> ктонибуть есть русский 08:07 <+bridge> @33370928: Привет, банан 08:36 <+bridge> https://youtu.be/bS-X5fJtfIw 09:06 <+bridge> axaxaxaxaxaxaxaxa 09:29 <+ChillerDragon> gh keybinds are full broken since forever and nobody is fixing them aaaa 09:29 <+ChillerDragon> it just opens random menus wtf 11:37 <+bridge> xd 12:36 <+bridge> uhh I got the 610 nvidia driver now as well :3 13:04 <+bridge> @heinrich5991 if you have a minute could you take a look at #12194 13:04 <+bridge> https://github.com/ddnet/ddnet/pull/12194 13:29 <+bridge> done 15:30 <+bridge_> works for me, which one is broken for you 15:35 <+bridge_> https://cdn.discordapp.com/attachments/293493549758939136/1512087330171916338/image.jpg?ex=6a22d090&is=6a217f10&hm=ee1dcccfa555ef4eb086e4004f73a4a7c4e4bf515b336eabb748c8c5b0767681& 15:35 <+bridge_> https://cdn.discordapp.com/attachments/293493549758939136/1512087330863714515/image.jpg?ex=6a22d090&is=6a217f10&hm=c4566714281a4154337f4be68334f0e070c6870aee60e51ce77e5af1afcc8343& 15:35 <+bridge_> https://cdn.discordapp.com/attachments/293493549758939136/1512087331228749965/image.jpg?ex=6a22d090&is=6a217f10&hm=0b6c708dfaa2997f4d19bb1afbdbdb6cc5873a6c162903d1af88694d3fc552df& 15:35 <+bridge_> https://cdn.discordapp.com/attachments/293493549758939136/1512087331576872980/image.jpg?ex=6a22d091&is=6a217f11&hm=fefd18f6737439fc40244b8d4a2702f1ba1efffb46519cf8946810df1d803c2f& 15:35 <+bridge_> @Discord Mod 16:19 <+bridge_> @learath2 I came up with a silly feature for my language(yoinked from Jai & Zig). It could be interesting to be able to represent source code types as values, so something like this: 16:19 <+bridge_> ```rust 16:19 <+bridge_> type Type { 16:19 <+bridge_> Builtin(...), 16:19 <+bridge_> Struct { 16:19 <+bridge_> fields: &[StructField] 16:19 <+bridge_> }, 16:19 <+bridge_> .. 16:19 <+bridge_> } 16:19 <+bridge_> 16:19 <+bridge_> fn foo() { 16:19 <+bridge_> let ty: Type = i32::ty(); 16:19 <+bridge_> // `ty` represents `i32` type, so probably `Type::Builtin(...)` 16:19 <+bridge_> } 16:19 <+bridge_> ``` 16:19 <+bridge_> 16:19 <+bridge_> Then it'd be possible to convert `Type` value back into types xd 16:19 <+bridge_> ```rust 16:20 <+bridge_> fn gimme_ty() -> Type { 16:20 <+bridge_> return Type::Builtin(/* somehow make it return i32 */); 16:20 <+bridge_> } 16:20 <+bridge_> 16:20 <+bridge_> // the function has signature fn() -> i32 16:20 <+bridge_> fn random_fn() -> gimme_ty() { 16:20 <+bridge_> } 16:20 <+bridge_> ``` 16:20 <+bridge_> 16:20 <+bridge_> But it would probably require some keyword to express values that are available at compile time. 16:20 <+bridge_> @learath2 I came up with a silly feature for my language(yoinked from Jai & Zig). It could be interesting to be able to represent source code types as values, so something like this: 16:20 <+bridge_> ```rust 16:20 <+bridge_> enum Type { 16:20 <+bridge_> Builtin(...), 16:20 <+bridge_> Struct { 16:20 <+bridge_> fields: &[StructField] 16:20 <+bridge_> }, 16:20 <+bridge_> .. 16:20 <+bridge_> } 16:20 <+bridge_> 16:20 <+bridge_> fn foo() { 16:20 <+bridge_> let ty: Type = i32::ty(); 16:20 <+bridge_> // `ty` represents `i32` type, so probably `Type::Builtin(...)` 16:20 <+bridge_> } 16:20 <+bridge_> ``` 16:20 <+bridge_> 16:20 <+bridge_> Then it'd be possible to convert `Type` value back into types xd 16:20 <+bridge_> ```rust 16:20 <+bridge_> fn gimme_ty() -> Type { 16:21 <+bridge_> return Type::Builtin(/* somehow make it return i32 */); 16:21 <+bridge_> } 16:21 <+bridge_> 16:21 <+bridge_> // the function has signature fn() -> i32 16:21 <+bridge_> fn random_fn() -> gimme_ty() { 16:21 <+bridge_> } 16:21 <+bridge_> ``` 16:21 <+bridge_> 16:21 <+bridge_> But it would probably require some keyword to express values that are available at compile time. 16:21 <+bridge_> Um, I don't quite get it. Sort of like reflection-ish? 16:21 <+bridge_> ye 16:22 <+bridge_> compile time reflection 16:22 <+bridge_> check out lean 😛 there's no distinction between types as values and other values ^^ 16:25 <+bridge_> but what sort of things could you do with it? maybe an example use might let me understand 16:26 <+bridge_> e.g. you can implement generics this way 16:27 <+bridge_> ```rs 16:27 <+bridge_> fn vec(T: Type) -> Type { 16:27 <+bridge_> return struct { 16:27 <+bridge_> ptr: *mut T, 16:27 <+bridge_> len: usize, 16:27 <+bridge_> cap: usize, 16:27 <+bridge_> } 16:27 <+bridge_> } 16:27 <+bridge_> ``` 16:27 <+bridge_> (hypothetical language, I think zig might actually do it similarly) 16:29 <+bridge_> your hypothetical language is pretty much exactly how zig would do it :kek: 16:29 <+bridge_> I don't really like the way generics work in Zig 16:30 <+bridge_> Hmm, but would this `vec` only be valid to call if T can be resolved at compile time? 16:30 <+bridge_> Or are we talking about truly first class Types here? 16:31 <+bridge_> T must be known at compile time, zig for example enforces that with through `comptime` - pretty neat 16:31 <+bridge_> T must be known at compile time, zig for example enforces that through `comptime` - pretty neat 16:31 <+bridge_> When types are passed as regular parameters there's no way to take an address of specialized(?) generic function. This is not possible in Zig(I don't think I ever used it but I don't like that I can't do it xd) 16:31 <+bridge_> ```rust 16:31 <+bridge_> fn foo(a: T, b: T) {} 16:31 <+bridge_> 16:31 <+bridge_> fn main() { 16:31 <+bridge_> let _ = &foo::; 16:31 <+bridge_> } 16:31 <+bridge_> ``` 16:32 <+bridge_> Is it criminal of me to say that I kinda like C++ templates more? I'm not a huge fan of compile time stuff looking like it just might work at runtime but not working 16:32 <+bridge_> Not a huge fan of SFINAE, but the fact that the template arguments are completely separate makes it very clear what is going on 16:32 <+bridge_> not at all, i think being explicit is a good thing 16:33 <+bridge_> rust has stuff that doesn't look like runtime, but also looks saner than C++ to me 16:34 <+bridge_> lean has truly first class types 16:34 <+bridge_> ngl I'm kinda enjoying the direction modern C++ is going, C++20 and 23 have brought a lot of great stuff 16:34 <+bridge_> C++23 has `std::expected` - love that 16:34 <+bridge_> C++23 has `std::expected` - love that 16:34 <+bridge_> 16:34 <+bridge_> oh and println 16:34 <+bridge_> which is a bad version of rust's `Result`, 8 years late ^^ 16:35 <+bridge_> Yeah the Rust approach is nice too 16:35 <+bridge_> 😆 16:35 <+bridge_> it could be possible to have a function that takes a type, let's say a struct, and modifies the fields in some way and returns it, and then it can be used as a type or something like this https://kristoff.it/blog/what-is-zig-comptime/#:~:text=%2F%2F%20I%20moved,typeName%28T%29%29%2C%20%7D%20%7D 16:35 <+bridge_> I don't like c++'s move semantics that force every non-copyable type to have some sort of internal null representation 16:35 <+bridge_> hyped for std::simd (slower than autovectorization) and std::linalg (surely won't be DOA) 🔥 16:35 <+bridge_> I use `#include ` extensively in all my new code, excellent implementation 16:36 <+bridge_> but no uhh 16:36 <+bridge_> Yeah I expect both of these to just not get used 16:36 <+bridge_> the new language features are all very cool 16:36 <+bridge_> some of the library additions are a bit ehh though 16:37 <+bridge_> https://hftuniversity.com/post/the-c-standard-library-has-been-walking-itself-back-for-fifteen-years-and-the-receipts-are-public was trending today and I agree with most of it 16:37 <+bridge_> except that the title doesn't really fit the content 16:39 <+bridge_> I really want to find a use for C++20 coroutines, I love coroutines 😄 16:40 <+bridge_> Why do you think it's worse than Rust's ``? (I don't really know much about the semantics of `std::expected`) 16:43 <+bridge_> go addict :p 16:44 <+bridge_> it was a bit inflammatory, rust has nicer optimizations around `enum`s 16:45 <+bridge_> plus `std::expected` can't fix c++'s move semantics referred to above 16:45 <+bridge_> `Result` and `String` have the same size in rust 16:46 <+bridge_> also i'm pretty sure on `std::expected` you can force UB by just ignoring the error xd 16:46 <+bridge_> also i'm pretty sure on `std::expected` you can force UB by just ignoring the error xd 16:46 <+bridge_> rust doesnt, you need to handle both explicitly 16:46 <+bridge_> ah, that's just how c++ likes to do things 16:46 <+bridge_> you can also force UB in rust, but at least you need to type more than not ignoring the error 16:46 <+bridge_> Does the specification of `std::expected` preclude "compact" representations like that? 16:47 <+bridge_> interesting question 16:47 <+bridge_> I've heard that the specification of `std::variant` precludes them, but I don't remember the arguments 16:49 <+bridge_> https://en.cppreference.com/cpp/utility/variant/valueless_by_exception maybe related to this? 16:50 <+bridge_> this is so terminally c++ 16:51 <+bridge_> > `std::list` is the canonical entry in this tier. Bjarne Stroustrup spent the 2012 GoingNative keynote showing that `std::vector` beats `std::list` even for the textbook "insertion in the middle of a large container" workload, because the linear scan dominates and the pointer chase punishes the cache. The follow-up post is titled, with deliberate emphasis, Are lists evil?. The answer is yes. `std::list` is not deprecated. It exists in the standa 16:51 <+bridge_> I guess this is one I disagree with, decades of computer science and we have still not grown past "goto is evil". 16:52 <+bridge_> Using a tool wrong doesn't mean the tool is useless, you could be using it wrong, you could be using it for the wrong thing. 16:53 <+bridge_> Perhaps they are just against that `std::list` approach of non-intrusive linked lists, but still. A `std::vector` is in a fair amount of cases is just not an analogue to a linked list 16:53 <+bridge_> hmmm. the question is if there's a good use case for `std::list` that's not served better by a dfferent data structure 16:53 <+bridge_> I'd say that that's implicit in the (clearly to some degree ironic) term "evil" 16:54 <+bridge_> as in, "evil" in this context means "very easy to use incorrectly" 16:54 <+bridge_> I'd say that unsigned integers are evil in C/C++ but obviously there are still use cases for them 16:54 <+bridge_> but maybe not everyone understands it this way 16:55 <+bridge_> Well the obvious one is when you need your references to stay valid and need the ability to insert at an arbitrary location. There aren't many data structures that give you this property, unless you are willing to take an extra indirection at every access 16:55 <+bridge_> hmm. that's quite abstract. can you give a more practical example? 16:55 <+bridge_> https://en.cppreference.com/cpp/utility/expected/operator%3D 16:56 <+bridge_> I don't understand what happens when the underlying move constructor throws an exception 16:56 <+bridge_> > If an exception is thrown, the old value is retained; `*this` does not become valueless. 16:56 <+bridge_> how is this achieved? 16:59 <+bridge_> Say I need a list of objects that need to be processed in some order, perhaps for priority purposes. If these entities also have cross references it would be invalid to move them when inserting a new one. 16:59 <+bridge_> A kernel scheduler is a decent usecase that comes to mind, there is an order you want to keep and tasks need to be regularly added and removed at different locations 17:00 <+bridge_> Or similarly a list that needs to be regularly spliced, perhaps to give different threads different sets of tasks 17:01 <+bridge_> hmm. feels like you need more state than just the list for this, to keep track of where you can insert elements of different priority 17:01 <+bridge_> so you don't have to scan the whole list for tht 17:02 <+bridge_> I suppose, but you probably can't handle `std::list` from several threads 17:02 <+bridge_> I guess if your argument is "there's use cases for linked lists", I agree 17:02 <+bridge_> I'm not sure there are use cases for `std::list` 17:03 <+bridge_> Sure, but other solutions will require that structure and will require an indirection for reference integrity 17:04 <+bridge_> Yeah, perhaps `std::list`s non intrusive approach is not the best, but I'm saying there are uses for linked lists 17:05 <+bridge_> I just have this intense distaste for people that speak in absolutes about stuff like this. Just because you don't know when it's appropriate to use something, doesn't mean it's useless 17:05 <+bridge_> I agree that there are definitely uses for linked list 17:05 <+bridge_> `std::list`, not so sure 17:18 <+bridge_> Actually quite curious, I can't quite imagine how you can do this in a move constructor without extra allocation 17:34 <+bridge_> MSVC uses a "guard", the old value of unexpected is saved into a temporary, the new expected value is move constructed, if it throws an exception the guard is never defused, and the destructor of the guard moves the old unexpected back into where it belongs 17:35 <+bridge_> ``` 17:35 <+bridge_> _Second _Tmp(_STD move(_Old_val)); 17:35 <+bridge_> if constexpr (!is_trivially_destructible_v<_Second>) { 17:35 <+bridge_> _Old_val.~_Second(); 17:35 <+bridge_> } 17:35 <+bridge_> 17:36 <+bridge_> _GuardTy<_Second> _Guard{_STD addressof(_Old_val), _STD addressof(_Tmp)}; 17:36 <+bridge_> _STD construct_at(_STD addressof(_New_val), _STD forward<_Args>(_Vals)...); 17:36 <+bridge_> _Guard._Target = nullptr; 17:36 <+bridge_> ``` 17:36 <+bridge_> 17:36 <+bridge_> Quite a useful pattern actually, I'd never seen a destructor used like that before to handle an exception 18:27 <+bridge_> @heinrich5991: still here? Not sure how to do what you suggested in https://github.com/ddnet/ddnet/pull/12250#issuecomment-4621983975 19:04 <+bridge_> https://youtu.be/1VyeSL243MU?si=VXfg-RdaPNGnfChV 21:49 <+bridge_> @blaiszephyr did our ticket get automatically closed due to some timeout 21:49 <+bridge_> öh i didnt close it, did the bot dm you? 21:50 <+bridge_> idk it just wasn't in my channel list 21:50 <+bridge_> well, i do know, and no it did not DM me 22:00 <+bridge_> how do textures work for all those slope shapes 22:01 <+bridge_> it kinda reminds me of what mario bros wonder does instead of tilesets 23:01 <+bridge_> what if the move in the destructor throws? 23:09 <+bridge_> the rust std lib does this in a lot of places, first time I see this pattern in C++: https://doc.rust-lang.org/std/mem/struct.DropGuard.html 23:16 <+bridge_> Iii did not dig that far down. I think they assume if the move in one direction didn't fail the other won't fail. So my guess is it crashes and burns 23:17 <+bridge_> and otherwise you get an `std::terminate` for double exception? 23:17 <+bridge_> meh 23:21 <+bridge_> ``` 23:21 <+bridge_> template 23:21 <+bridge_> requires is_nothrow_move_constructible_v<_Uty> 23:21 <+bridge_> struct _NODISCARD _GuardTy { 23:21 <+bridge_> constexpr _GuardTy(_Uty* _Target_, _Uty* _Tmp_) noexcept : _Target(_Target_), _Tmp(_Tmp_) {} 23:21 <+bridge_> constexpr ~_GuardTy() noexcept { 23:21 <+bridge_> if (_Target) { 23:21 <+bridge_> _STD construct_at(_Target, _STD move(*_Tmp)); 23:21 <+bridge_> } 23:21 <+bridge_> } 23:21 <+bridge_> _Uty* _Target; 23:21 <+bridge_> _Uty* _Tmp; 23:21 <+bridge_> }; 23:21 <+bridge_> ``` 23:21 <+bridge_> This is how they handle it, the type needs to be nothrow move constructible 😛 23:21 <+bridge_> and what if it isn't? 23:22 <+bridge_> I didn't see that restriction in `std::expected` 23:23 <+bridge_> I don't see another case for it, so I'm guessing it's a restriction we just missed? 23:24 <+bridge_> https://en.cppreference.com/cpp/utility/expected/operator%3D 23:24 <+bridge_> ah yea 23:25 <+bridge_> > at least one of the following is true: 23:25 <+bridge_> > 23:25 <+bridge_> > - `T` is (possibly cv-qualified) `void` 23:25 <+bridge_> > - `std::is_nothrow_move_constructible_v` 23:25 <+bridge_> > - `std::is_nothrow_move_constructible_v` 23:25 <+bridge_> yea, that unbreaks that type I guess 23:25 <+bridge_> `std::variant` doesn't have it, which is why it has that error state 23:26 <+bridge_> ah. I know why `std::expected` can't implement this niche optimization of `Result` ^^ 23:26 <+bridge_> stable ABI 23:27 <+bridge_> whatever ABI they choose now will have to be supported forever, and I doubt they start off by choosing one with niches 23:27 <+bridge_> (practical reason, not a theoretical one) 23:28 <+bridge_> I guess that is just very confusing in general though 23:28 <+bridge_> Though I don't know the exact mechanics of `Result` either, so maybe those are as confusing as this too 😄 23:29 <+bridge_> `Result` is very simple 23:30 <+bridge_> `enum Result { Ok(T), Err(T) }` 23:30 <+bridge_> rust's support for algebraic data types helps here. and that moves in general are always `memcpy`s 23:32 <+bridge_> I guess there is no throwing in Rust to create this mess to begin with 23:33 <+bridge_> no throwing on moves 23:33 <+bridge_> there's throwing in general (called `panic`) 23:33 <+bridge_> Eeeh, can a panic even be caught? 23:33 <+bridge_> yes 23:33 <+bridge_> https://doc.rust-lang.org/std/panic/fn.catch_unwind.html 23:34 <+bridge_> There must be a downside to this though, as far as I can see all these requirements are fundamental 23:35 <+bridge_> I guess Rust moves being just `memcpy` instead of the "resource stealing" C++ kind is the downside 23:35 <+bridge_> yes, the downside is that you can't run custom code when something moves 23:35 <+bridge_> (unless you wrap the type appropriately, but that's a really good default IMO) 23:36 <+bridge_> I guess needing all the Pin/Unpin semantics is a consequence of having less control over what moves do 23:36 <+bridge_> Yeah custom moves are very confusing for beginners 23:36 <+bridge_> they also seem to be confusing for us 😉 23:36 <+bridge_> I see so many useless/broken "move" constructors 23:37 <+bridge_> we had to dig deep to understand why `std::expected` works 23:37 <+bridge_> https://doc.rust-lang.org/std/pin/struct.Pin.html can be used to opt out of `memcpy` moves 23:37 <+bridge_> The confusing part isn't the move semantics though, it's how it interacts with the shitty exceptions and generic types that need to handle both copy and move constructors 23:37 <+bridge_> it doesn't have nice language support (yet) 23:38 <+bridge_> yes. the other bad thing about c++'s move semantics is that the type needs an "empty" state 23:38 <+bridge_> I really like that the rust compiler tracks that for me, at less runtime cost than C++'s approach 23:38 <+bridge_> because rust just knows which objects are initialized and which are not 23:39 <+bridge_> so the objects don't need to keep that state by themselves 23:39 <+bridge_> Does it really need an empty state? I thought using objects that are moved out of are explicitly UB so you don't need to have an explicit empty state 23:39 <+bridge_> Does it really need an empty state? I thought using objects that are moved out of is explicitly UB so you don't need to have an explicit empty state 23:41 <+bridge_> AFAIK it's an undefined but valid state 23:41 <+bridge_> anyway, at least the destructor needs to run on that state 23:41 <+bridge_> Maybe I don't understand move semantics as well as I thought 😄 23:41 <+bridge_> so that the object itself needs to track in one way or another whether it still needs to free some resource 23:41 <+bridge_> Though tbf I never claimed to be an expert in C++. I'm more a C person 23:41 <+bridge_> so that the object itself needs to track in one way or another whether it still needs to free its resources 23:42 <+bridge_> in practice, there aren't many valid states to choose from when you want to be performant. it needs to be some sort of "empty" state 23:44 <+bridge_> Yeah it does need to be a valid state, idk how I forgot this 23:44 <+bridge_> it's one of the reasons `std::unique_ptr` needs to have a `nullptr` state, same for `std::shared_ptr` 23:45 <+bridge_> in rust, the compiler tracks this for me, so there doesn't need to be an "empty" `Box` state 23:47 <+bridge_> There are some really cool things in Rust 23:47 <+bridge_> it had the opportunity to learn from mistakes of earlier languages 23:48 <+bridge_> All these niceties also bring in a necessity for borrowck though, which just does not vibe with me at all 23:48 <+bridge_> I'm sure it gets better as you use it more and more but it just instantly takes me out of my coding flow 23:48 <+bridge_> algebraic data types? proper move semantics? 23:48 <+bridge_> those don't need a borrow checker I think 23:50 <+bridge_> ADTs no, but for the move semantics you do, no? I mean unless you want everything to be owned 23:52 <+bridge_> I don't think you need this for move semantics 23:52 <+bridge_> pointers/references would not be owned, everything else would be owned 23:52 <+bridge_> By proper move semantics do you mean just nothrow, non custom code moves? 23:53 <+bridge_> plus the compiler tracking which variables need drop 23:53 <+bridge_> plus the compiler tracking which variables need destructors 23:53 <+bridge_> heino present