5 ms·
[flagged]
by 38 2y ago
[flagged]
- mystified5016 2y agoDon't use Go, use C#. The same code is even more terse: List<T> lst = new(); lst.Add(obj); Done. Sure go is nice if you only care about being hip and trendy, but C# is better in more situations than you'd think. Why bother with new languages and absurd syntax when C# has been around for decades and has perfectly clear syntax that spells out exactly what you want in simple English? Don't be an asshole. "Just use today's trendy language instead of crusty old C++" makes you an asshole. Stop it.
- deleted 2y ago[deleted]
- Seattle3503 2y agoI write Rust mostly, but still found the article interesting and accessible.
- FpUser 2y ago>or maybe just dont use C++ Or maybe use whatever the fuck you want and let other to decide for themselves. Often people even do not have a choice.
- Joker_vD 2y ago> Or maybe use whatever the fuck you want and let's other to decide for themselves. Often people even do not have a choice. And you know why they don't have the choice to not use C++? Because someone else made a choice to use C++ and so here we are. That's the paradox of having a freedom in chosing the language: only the first contributor has that freedom, everyone else either has to accept their decision, or leave.
- gumby 2y agoWTF is "modern syntax"?
- Joker_vD 2y agoProbably "the types go after the variable name in the declaration"? But even that actually pre-dates C.
- deleted 2y ago[deleted]
- mgaunard 2y agoClearly you didn't understand what emplace does. Go is simpler because it is limited in functionality.
- IshKebab 2y agoIf you want simple you can just you push_back everywhere. It's what everyone did for decades.
- jcelerier 2y agoThis is either making a copy or storing things as pointers with an indirection penalty, how do you do in go if you want to add an object to a container without making a copy or without indirection
- leecommamichael 2y agoI’m not interested in who you’re replying to, but your question seems to imply there’s some way to add data to a container without already having some copy. That’s only possible if the container (and it’s data) is static. The discrimination is the penalty of copying a stack-value vs some GC’d/managed memory. Then if you compare those scenarios the semantics lead to more interesting topics for debate.
- pavlov 2y ago> “some way to add data to a container without already having some copy” That’s what C++ vector emplace_back does. It allocates the memory if needed, then constructs the object in place using the provided arguments. No need for a copy.
- leecommamichael 2y agoI'm interested in the semantics of "if needed" and "constructs in place." I'm confused as to how you could initialize dynamic memory without a copy. I feel like initializing dynamic memory _is_ copying (memcpy), unless we're talking about some sort of fully constexpr thing.
- jcelerier 2y agobasically: #include <format> #include <string> #include <vector> struct my_type { std::string data; my_type(int value): data(value, 'A') { puts("int ctor\n"); } my_type() { puts("default ctor\n"); } my_type(const my_type&) { puts("copy ctor\n"); } my_type(my_type&&) noexcept { puts("move ctor\n"); } my_type& operator=(const my_type&) { puts("copy assign\n"); return *this; } my_type& operator=(my_type&&) noexcept { puts("move assign\n"); return *this; } }; int main() { puts("Case A\n"); { std::vector<my_type> vec; vec.emplace_back(123); } puts("Case B\n"); { std::vector<my_type> vec; vec.push_back(my_type{123}); } } Here, in the first case (in a very schematic way), the vector: 1/ allocates the memory for an element in for instance: my_type* mem_begin = std::allocator<my_type>::allocate(...); 2/ calls std::construct_at(&mem_begin[0], 123); which directly creates the object and calls my_type::my_type(int) constructor in the std::vector's memory storage. The only output you'll see will be int ctor The inner std::string will also be initialized directly in the right memory position, at no point there will be a copy of, say, 10000 'A' characters. In the second case, first you construct my_type on the stack of the calling code so you get a first call to my_type::my_type(int) Then my_type is moved (or copied, if it didn't have move constructors): std::vector's implementation does pretty much the same thing, but the result is two constructions instead of one: my_data* mem_begin = std::allocator<my_data>::allocate(...); ... std::construct_at(&mem_begin[0], instance_of_my_data_passed_in_argument); which ends up calling my_type::my_type(my_type&&) ; you'll see int ctor move ctor and the inner string will be copied / moved too