u/grishavanika

280 post karma · 801 comment karma · joined Mar 2014

  1. 9points

    (C++) Asynchronous API (grishavanika.github.io)

    0 comments · source

  2. 36points

    Asynchronous API (grishavanika.github.io)

    28 comments · source

  3. 58points

    A Library Approach to Constant Template Parameters (brevzin.github.io)

    10 comments · source

grishavanika

what is the implementation of co::alloc? is it possible to implement co::is_allocated_ptr(void*)?

grishavanika

updated, thank you

grishavanika

Ah, I dint update the article code (github version is nothrow). The intention was to basically have noexcept CURL_async_get() for simplicity. You are right, will add a point of everything noexcept. Introducing errors handling adds another layer of complexity. I planned to have this discussed separately, but it's too much

grishavanika

it is around CURL_async_get intentionally to cover any other C-style API, which, in general case, you dont know the implementstion details. I mentioned at one point that this way you build unoptimized API, causing multiple unneeded allocations, implying building CURL wrapper targetting senders would be better.

Regarding error reporting - CURL API never fails, new is nothrow + assert, so it never fails too, start has no need to hndle exceptions since there are zero

grishavanika

How can I understand where and when code is executed?

The code in the article is intentioinally one self-contained main.cc file for every section, with nothing more then calls to a CURL library. It should be trivial to put breakpoints to see how it executes. So bunch of the code is executed in CURL internals which we call inside CURL_AsyncScheduler::tick(), which is called in a loop from within a main.

For callbacks, the section is implementing with libcurl multi which also links to main.cc.

This async style is intentionally trying to hide things behind abstractions to help people write more of it

It is, but the article builds everything from scratch to be able to step thru all of the abstractions - intentionally, so there is a way to see all of the layers. (Well, maybe CURL API hides the system specific calls, but it's close to what needs to be done using system API).

What ways are there to make some requests high priority, normal priority, or low priority? Not only at the CPU level but at the I/O level.

I don't have good answer since most of the time I used only IOCP.

grishavanika

its all about composition of async tasks. Callbacks are fine and manageable up until some point. See the examples at the end. Callbacks section hand codes the state machine; coroutines, fibers and senders allow to express the same state machine with less code which is also arguably easier to maintain and extend. "returning an object" is covered by a Task section. Its usage also requires to hand code state machine manually.

- callbacks - https://grishavanika.github.io/async_api.html#app_callbacks
- vs coroutines - https://grishavanika.github.io/async_api.html#app_coroutines
- vs tasks polling - https://grishavanika.github.io/async\_api.html#app\_futures

The complexity of implementing everything is true complain, but this is usually hidden from the user... compilation errors.. maybe this is a tradeoff you need to pay for benefits provided ;)

grishavanika

Cool stuff. Question: if we are adding consteval blocks and code injection, can we special case templates as in "this code injection should work as if written by user, no weird workarounds"? Or that's the whole problem to be solved?

grishavanika

Talking about domain, how it it different to have "category"+"severity", i.e., what UnrealEngine does:

DEFINE_LOG_CATEGORY(MotionPlanner);
UE_LOG(MotionPlanner, Warning, TEXT("Planning trajectory..."));

?

grishavanika

Unreal does for any and all type T, stl only for trivial* that makes sense to memcopy

grishavanika

See, the main reason why this is faster is that Unreal’s TArray allocates at least 4 elements

Unsure this is the main win. The main difference is that TArray assumes the elements are relocatable: so realloc/memcpy is used under the hood without having a need to do per-element move on resize.

grishavanika

From the author in comments:

This article is _not about ranges_. It is about the bureaucratic process of the standardization committee breaking down and failing to deliver the highest levels of quality for big-ticket features. This is explained at the end.

grishavanika

In summary, when you consider that coroutines are for implementing “asynchronous functions”, you will conclude that all the gotchas discussed apply to “asynchronous functions” due to their nature. Coroutines do not really add significantly new gotchas.

Hence, coroutines are "fine". I feel like there is logical error leading to such conclusion. The criticism of coroutines come from the fact that it was a new addition, hence there was an opportunity to "try fix/help with" existing issues. I.e. adding "async" to coroutine function declaration may help figuring out that function has extra requirements, etc

grishavanika

I'm collecting some from time to time: https://grishavanika.github.io/cpp_tips_tricks_quirks.html

grishavanika

Example at 1:08:23 with assert ODR is actually a linker error, foo is defined multiple times, https://godbolt.org/z/efWGc7nba

grishavanika

At the end I ended up with something like this: https://godbolt.org/z/dbW3T1hcE

void log(const char* file, unsigned line, FormatBuffer::format_result&& fmt)
{
    fmt.out.finish(fmt.size);
    std::println(stderr, "'{},{}' -> {}", file, line, fmt.out.str());
}

#define MY_LOG(fmt, ...) log(__FILE__, __LINE__ \
    , std::format_to_n(::FormatBuffer{}.it()\
        , ::FormatBuffer::available_size()  \
        , fmt, ##__VA_ARGS__))

So instantiating std::format_to_n<Args...> instead of log<Args...>?

grishavanika

DR20: std::make_format_args now accepts only lvalue references instead of forwarding references

But now simple code like this does not work:

void Handle(std::format_args&& args) { }

Handle(std::make_format_args(10, 'c'));

(That happens when you try type-erase into std::format_args from, for example, debug macros).

Even cppreference example has "user-friendly" unmove just for that: https://en.cppreference.com/w/cpp/utility/format/make_format_args.html

template<typename T>
const T& unmove(T&& x)
{
    return x;
}

int main()
{
    // ...
    raw_write_to_log("{:02} │ {} │ {} │ {}",
                     std::make_format_args(unmove(1), unmove(2.0), unmove('3'), "4"));
}
grishavanika

Yes, you have explicit API to add and get, don't quite understand what map gives and why operator< needs to be implemented in a tricky way as a workaround

grishavanika
bool operator<(const interval& x, const interval& y)
{
  if(x.min == y.min) {
    if(x.max != y.max) throw interval_overlap();
    return false;
  }

That all looks terrible. Why not std::vector<interval> and go with that?

grishavanika

std::deque, however I'm not sure what do you mean 20% faster. Compared to vector? I doubt

grishavanika

pointer and reference parameters in function

It's commonly a surprise, you could have return by value and accept by value just fine:

 struct Foo;
 Foo F(Foo);
grishavanika

I don't get the argument anyway:

// The first one (1)
ostream<char>&
  operator<<(ostream<char>& os, const char* s);

If we tried to make (1) a member function of std::basic_ostream<char, Traits>, we’d also need to re-define everything else for that specialization

Can't we just have member function for generic CharT:

ostream<char>& operator<<(const CharT* s);

and specialize it for const char*? Was it not an option in 1998?: https://godbolt.org/z/e4xGf9zch

#include <cstdio>
template<typename CharT>
struct ostream
{
    ostream& operator<<(const CharT*)
    { std::puts("generic"); return *this; }
};

template<>
ostream<char>& ostream<char>::operator<<(const char*)
{ std::puts("const char*"); return *this; }

int main()
{
    ostream<char> o;
    o << "str";
    ostream<wchar_t> w;
    w << L"x";
}

And you can’t just define member functions that take CharT and const CharT* and be done with it: there are certain character types the Committee didn’t want to be clever with, or otherwise didn’t want to support printing period.

So, probably, this, but I still am not sure I get it. Is instantiating basic_ostream<signed char> was not desired or it's fine, but operator<<(const signed char*) was not wanted?

why are any of them member functions?

Well, I don’t have an answer to this one and, either way, that ship has sailed. I guess the folks at the Standards Committee really liked member functions and wanted to salvage what they could here?

so we don't know, I guess?

grishavanika

Ok, went to see what latest std says. Indeed

optional& operator=(optional&& rhs)
Postconditions: rhs.has_value() == this->has_value()
optional(optional&& rhs)
Postconditions: rhs.has_value() == this->has_value()

The oldest paper where optional is mentioned that I found is http://wg21.link/n4529, from where this same postcondition comes, I guess:

optional(optional<T>&& rhs)
Postconditions: bool(rhs) == bool(*this)

So this is explicitly choosen design. There is an older reddit thread with discussion oin this topic: https://www.reddit.com/r/cpp/comments/75paqu/design_decision_for_stdoptional_moved_from_state/