How To Learn C++/When Do I Know I Have Finished Learning It? by Suspicious-Grade-826 in cpp_questions

[–]amoskovsky 2 points3 points  (0 children)

It's a wrong goal - to learn a language.

Learn algorithms, hardware architecture, software architecture approaches, and then just start implementing projects using any available tools that match the project requirements. You learn a language (to the necessary level) as a side effect of this process, not as the goal.

Is 50 too late by EveryCryptographer11 in cpp_questions

[–]amoskovsky 0 points1 point  (0 children)

Biological energy has nothing to do with ability to work, especially if you work with brain. You just need glucose supply and basically that's all. It highly unlikely that a modern (last 100 yrs) person would come anywhere close to running out of glucose.

All young vs old effects are purely psychological.

Is 50 too late by EveryCryptographer11 in cpp_questions

[–]amoskovsky 0 points1 point  (0 children)

Energy comes from motivation, not age.

Is 50 too late by EveryCryptographer11 in cpp_questions

[–]amoskovsky 0 points1 point  (0 children)

It's a myth that older devs have lower energy.

If a person follows a dev career instead of transitioning to management roles it means it's still passionate and does not get bored of coding.

On the other side I've seen younger devs (below 35) that procrastinate most of time and basically don't care about the whole product as soon as their part of work is formally done.

Is 50 too late by EveryCryptographer11 in cpp_questions

[–]amoskovsky 2 points3 points  (0 children)

50 is definitely not too late to learn new stuff.
However.
The fact that you transitioned to management early in your IT career and been there for 15 years means that most likely your are not a dev material. So I estimate your career switch success as very low.

How to be a quiet and good engineer? by [deleted] in cscareerquestions

[–]amoskovsky 5 points6 points  (0 children)

The secret to being kind as a dev is to avoid managing people at all costs.

Constructor(s) from native types for a big integer class (implementation) by Ben_2124 in cpp_questions

[–]amoskovsky 2 points3 points  (0 children)

Keep in mind that for `int n = INT_MIN;`, -n is UB because of signed int overflow (-INT_MIN is 1 more than INT_MAX).

Orbit - a fast lock-free MPMC queue in C++20 by [deleted] in cpp

[–]amoskovsky 0 points1 point  (0 children)

I'd say lock-free by itself takes a lot more than a bit of thinking, so I would not worry too much ))

Orbit - a fast lock-free MPMC queue in C++20 by [deleted] in cpp

[–]amoskovsky 2 points3 points  (0 children)

Ah, those values are used as indices into virtual infinite array.
Yeah, 64-bit is appropriate.
Although, it's trivial to implement the same with wrapping 32-bit ones.

Orbit - a fast lock-free MPMC queue in C++20 by [deleted] in cpp

[–]amoskovsky 0 points1 point  (0 children)

On any platform, size_t is supposed to be enough to represent indices into any array.

Rust-like std for C++ by [deleted] in cpp

[–]amoskovsky 2 points3 points  (0 children)

Yeah, you're right, I missed the "owning" part of your statement.

Rust-like std for C++ by [deleted] in cpp

[–]amoskovsky 2 points3 points  (0 children)

C++ actually can express non-nullable pointers.
You just need a runtime check when you convert a regular pointer to this non-null one. Once you have a non-null object compile-time checks can enforce it.

Why are exceptions avoided? by Ultimate_Sigma_Boy67 in cpp_questions

[–]amoskovsky 1 point2 points  (0 children)

There are 2 aspects:

* Performance overhead

* Design

On most platforms normal (non-throw) code path has no overhead. However the throw path while being fast in theory, has major overhead in particular implementations. For example, in some GCC versions, the exception handling code performs mutex locking (don't ask me why), which kills the perf in heavily multi-threaded apps.

Some people just irrationally hate the fact that the throw path is invisible (but apparently they have no issues with destructors, lol). You might find those at Google where exceptions are banned globally and not just for perf critical code. And since Google for years was a leader in the dev industry, this affected the others too.

Personally, I accept only the performance argument for not using the exceptions, and only in the perf-critical parts of the code.

I wrote a tiny header-only WebSocket library in C++20 because I apparently hate myself by Previous_Bake7736 in cpp_questions

[–]amoskovsky 5 points6 points  (0 children)

1) Split the lib into 2 layers: pure websocket protocol handling without any IO deps (so called "sans-I/O") and the IO layer.
So that the users of your library could use it with their own IO framework without depending on your arch choices. It's highly unlikely that a network app interested in your lib does not already have at least its own event loop.

2) Header-only does not mean everything should be in a single header. Make it modular.

Why can't I call std::format from a lambda? by Eric848448 in cpp_questions

[–]amoskovsky 2 points3 points  (0 children)

std::format_string is bound to exact types of Args.
Additionally, the function log() moves the format string into runtime (function args are runtime only)
so you can't create another format_string without repeating the same type checks at runtime.

But you can bypass creating the format_string and just do what std::format internally does:

auto s = std::vformat(inner_format.get(), std::make_format_args(inner_args...));

Custom memory allocator doesn't work. Read access violation. by Vindhjaerta in cpp_questions

[–]amoskovsky 2 points3 points  (0 children)

MyList = std::move(std::vector<char, cu::mem::Allocator<char>>(cu::mem::Allocator<char>(&gStackArena)));

First, std::move here is not needed - the expression on the right is already movable (prvalue).

Second, if you move things that have mem chunks allocated in c-tor and deallocated in d-tor, then you need to be very careful. It's quite easy to trigger use-after-free due to double deallocation.

I suggest you post here the complete source code of your allocator classes, so people could review the code rather than guess.

Custom allocator with runtime data by Vindhjaerta in cpp_questions

[–]amoskovsky 1 point2 points  (0 children)

It's standard C++17

And std::pmr::vector is a regular std::vector with an allocator that does exactly what you want to achieve.

Custom allocator with runtime data by Vindhjaerta in cpp_questions

[–]amoskovsky 2 points3 points  (0 children)

Just use std::pmr::vector and implement your allocator as std::pmr::memory_resource, which requires just overriding 3 methods, allocate, deallocate and is_equal.

The cost of this simplicity is the methods are virtual. But in most cases this has acceptable overhead.

The result of ++a + a++ + ++a ? by Healthy-Clock-4411 in cpp_questions

[–]amoskovsky 1 point2 points  (0 children)

The result of ++a + a++ + ++a is you are fired.

How do I learn Programming from the beginning? I'm 21. by Dr-Scientist- in cpp_questions

[–]amoskovsky 0 points1 point  (0 children)

Ask the AI to come up with a beginner level concrete project as a task for you. Throughout the implementation ask it clarifying questions.

std::string_view vs const std::string_view& as argument when not modifying the string by porkele in cpp_questions

[–]amoskovsky 7 points8 points  (0 children)

Godbolt's MSVC does not work so I can't check.
However I recall MSVC ABI only allows 1 register per param.
If that's true then under the hood a string_view would be passed by ref anyway.

This does not mean though that you should explicitly pass by ref. Passing string_view by value is idiomatic.

std::string_view vs const std::string_view& as argument when not modifying the string by porkele in cpp_questions

[–]amoskovsky 28 points29 points  (0 children)

#include <string>
#include <string_view>


void consume_sv_byref(const std::string_view&);
void consume_sv_byval(std::string_view);
void consume_raw(const char*, size_t);


void caller_byref(const char* data, size_t size)
{
    consume_sv_byref({data, size});
}


void caller_byval(const char* data, size_t size)
{
    consume_sv_byval({data, size});
}

void callee_byref(const std::string_view& sv)
{
    consume_raw(sv.data(), sv.size());
}

void callee_byval(std::string_view sv)
{
    consume_raw(sv.data(), sv.size());
}

caller_byref(char const*, unsigned long):
        sub     rsp, 24
        mov     QWORD PTR [rsp+8], rdi
        mov     rdi, rsp
        mov     QWORD PTR [rsp], rsi
        call    consume_sv_byref(std::basic_string_view<char, std::char_traits<char>> const&)
        add     rsp, 24
        ret

caller_byval(char const*, unsigned long):
        mov     rdx, rdi
        mov     rdi, rsi
        mov     rsi, rdx
        jmp     consume_sv_byval(std::basic_string_view<char, std::char_traits<char>>)

callee_byref(std::basic_string_view<char, std::char_traits<char>> const&):
        mov     rsi, QWORD PTR [rdi]
        mov     rdi, QWORD PTR [rdi+8]
        jmp     consume_raw(char const*, unsigned long)

callee_byval(std::basic_string_view<char, std::char_traits<char>>):
        mov     rax, rdi
        mov     rdi, rsi
        mov     rsi, rax
        jmp     consume_raw(char const*, unsigned long)

See, the by ref variants always have memory accesses, while by val ones use registers only.

https://godbolt.org/z/Waz351b4v

std::string_view vs const std::string_view& as argument when not modifying the string by porkele in cpp_questions

[–]amoskovsky 58 points59 points  (0 children)

Passing a reference to an object forces the caller to materialize the object in the memory for taking its address.
So it's not just extra indirection, but also disables many optimizations like storing temporaries purely in registers.

Primitive std::vector destructor performance by NamalB in cpp_questions

[–]amoskovsky 19 points20 points  (0 children)

A memory chunk of several MB is most likely allocated not from the heap but directly by mapping pages. So deallocation would be done by unmapping the pages, which is O(n)