C++ Russia: how it was

If at the beginning of the play you mention that there is a code in C++ hanging on the wall, by the end it must inevitably shoot you in the foot.

Bjarne Stroustrup

From October 31 to November 1, the C++ Russia Piter conference took place in St. Petersburg – one of the largest programming conferences in Russia, organized by JUG Ru Group. Among the invited speakers are members of the C++ standardization committee, speakers from CppCon, authors of books from O’Reilly, as well as maintainers of projects like LLVM, libc++, and Boost. The conference is aimed at experienced C++ developers looking to deepen their expertise and share experiences in live communication. Students, graduate students, and university instructors are offered very pleasant discounts.

The Moscow edition of the conference will be held in April next year, and for now, our students will share what interesting things they learned at the recent event. 

C++ Russia: how it was

Photos from the conference album

About Us

This post was created by two students from HSE — St. Petersburg:

  • Liza Vasilienko – a fourth-year undergraduate student studying 'Programming Languages' as part of the 'Applied Mathematics and Computer Science' program. After getting acquainted with C++ in her first year at university, she later gained practical experience through internships in the industry. Her passion for programming languages in general and functional programming in particular influenced her choice of talks at the conference.
  • Danya Smirnov – a first-year master's student in 'Programming and Data Analysis'. Back in school, he wrote C++ tasks for competitions, and somehow, the language kept coming up in his studies and eventually became his main working language. He decided to participate in the conference to strengthen his knowledge and to learn about new possibilities.

In the mailing list, the faculty leadership often shares information about educational events related to our specialty. In September, we saw information about C++ Russia and decided to register as attendees. This is our first experience of participating in such conferences.

Conference structure

  • Reports

Over the course of two days, experts read 30 reports covering many hot topics: clever applications of language features to solve practical problems, upcoming language updates in light of the new standard, compromises in C++ design and precautions when dealing with their consequences, examples of interesting project architectures, as well as some underlying details of the language's infrastructure. Concurrently, there were three presentations, most often two in Russian and one in English.

  • Discussion zones

After the presentations, any unanswered questions and unfinished discussions were moved to specially designated communication zones with the speakers, equipped with whiteboards. A great way to pass the break between sessions with pleasant conversation.

  • Lightning Talks and informal discussions

If someone wanted to give a short talk, they could sign up on the whiteboard for the evening Lightning Talk and get five minutes to talk about anything related to the conference theme. For example, a quick introduction to sanitizers for C++ (which was new to some) or a story about a bug in sine wave generation that can only be heard, not seen.

Another format was the panel discussion "Committee Confessions." On stage were some members of the standardization committee, and on the projector was a fireplace (officially for creating a cozy atmosphere, but the reason 'because EVERYTHING IS ON FIRE' seemed funnier), with questions about the standard and the general vision of C++, without heated technical discussions or flame wars. It turned out that the committee also has real people who may not be completely sure about something or might not know it.

For those who love debates, there was a third event — a BOF session "Go vs. C++." We take a Go enthusiast and a C++ aficionado, and before the session starts, they prepare a mountain of slides on topics (like package issues in C++ or the absence of generics in Go), and then they lively discuss among themselves and with the audience, who are trying to understand both points of view at once. If a side debate starts that is off-topic, the moderator intervenes and reconciles the parties. This format draws you in: several hours into the session, only half the slides had been covered. The end had to be rushed significantly.

  • Partner booths

The conference partners were showcased in the halls — at their booths, they discussed current projects, offered internships and job placements, conducted quizzes and small competitions, and also raffled off nice prizes. Moreover, some companies even provided the opportunity to go through initial interview stages, which could be beneficial for those who came not only to listen to the presentations.

Technical Details of the Presentations

We listened to presentations for both days. Sometimes it was difficult to choose one presentation from the parallel sessions — we agreed to split up and share the knowledge gained during breaks. Even so, it seems that much was still missed. Here, we would like to discuss the content of some presentations that we found most interesting.

Exceptions in C++ Through the Lens of Compiler Optimizations, Roman Rusiyaev

C++ Russia: how it was
Slide from the presentation

As the title suggests, Roman examined exception handling through the example of LLVM. For those not using Clang in their work, the presentation can still provide some insight into how code can potentially be optimized. This is the case because compiler developers and the corresponding standard library developers communicate with each other, and many successful solutions may overlap.

Thus, handling exceptions requires multiple actions: calling the handling code (if any) or freeing resources at the current level and unwinding the stack higher up. All of this leads to the compiler adding extra instructions for potentially exception-throwing calls. Therefore, even if an exception is not actually thrown, the program still performs unnecessary actions. To somehow reduce the overhead, LLVM has several heuristics for determining situations where exception handling code does not need to be added or where the number of 'extra' instructions can be reduced.

The speaker examines about a dozen such situations and shows both where they help accelerate program execution and where these methods are not applicable.

Thus, Roman Rusiyaev leads the listeners to the conclusion that code involving exception handling cannot always be executed with zero overhead and provides the following tips:

  • When developing libraries, it's best to avoid exceptions altogether.
  • If exceptions are necessary, it's advisable to add noexcept (and const) modifiers wherever possible, to allow the compiler to optimize as much as possible.

In general, the speaker confirmed the view that exceptions should be used minimally or avoided entirely.

The presentation slides are available at the link: [“C++ Exceptions Through the Lens of LLVM Compiler Optimizations”]

Generators, coroutines and other brain-unrolling sweetness, Adi Shavit

C++ Russia: how it was
Slide from the presentation

One of many talks at this conference, focused on the innovations of C++20, stood out not just for its colorful presentation but also for clearly outlining existing issues with collection handling logic (for loop, callbacks).

Adi Shavit highlights the following: current methods process the entire collection and do not provide access to some internal intermediate state (or do so in the case of callbacks, but with many unpleasant side effects, such as Callback Hell). It seems there are iterators, but they are not without their issues: there are no common entry and exit points (begin → end versus rbegin → rend, and so on), and it's unclear how long we will be iterating. Starting with C++20, these problems are being solved!

The first option: ranges. By wrapping around iterators, we get a common interface for starting and ending iterations, as well as the ability to compose. All this allows us to easily build full-fledged data processing pipelines. But it's not all smooth: part of the logical computations is contained within the implementation of a specific iterator, which can complicate code readability and debugging.

C++ Russia: how it was
Slide from the presentation

Well, for this case, C++20 introduces coroutines (functions whose behavior resembles generators in Python): execution can be delayed, returning some current value while preserving the intermediate state. Thus, we achieve not only working with data as it becomes available, but also encapsulate all logic within a specific coroutine.

But there’s a downside: currently, they are only partially supported by existing compilers, and the implementation isn't as neat as one might hope. For example, it's best to avoid using references and temporary objects in coroutines for now. Additionally, there are some limitations regarding what can be coroutines, and constexpr functions, constructors/destructors, and main are not included in this list.

Thus, coroutines address a significant portion of the issues related to the simplicity of data processing logic, but their current implementations require further refinement.

Materials:

C++ Tricks from Yandex.Taxi, Anton Polukhin

In my professional activities, I sometimes need to implement purely auxiliary tasks: a wrapper between an internal interface and the API of some library, logging, or parsing. Typically, there's no need for additional optimization. But what if these components are used in some of the most popular services on the Russian internet? In that case, we’ll have to process terabytes of logs per hour! Every millisecond counts, which is why various tricks must be employed — about which Anton Polukhin spoke.

Arguably, the most interesting example was the implementation of the pointer-to-implementation (pimpl) pattern. 

#include <third_party/json.hpp> //PROBLEMS! 
struct Value { 
    Value() = default; 
    Value(Value&& other) = default; 
    Value& operator=(Value&& other) = default; 
    ~Value() = default; 

    std::size_t Size() const { return data_.size(); } 

private: 
    third_party::Json data_; 
};

In this example, the initial goal is to eliminate the header files of external libraries — this will speed up compilation and protect from potential name conflicts and other similar errors. 

Alright, we've moved the #include to the .cpp file: we need a forward declaration of the wrapped API, as well as std::unique_ptr. Now we have dynamic allocations and other unpleasant things like scattered data in the heap and reduced guarantees. std::aligned_storage can help with all of this. 

struct Value { 
// ... 
private: 
    using JsonNative = third_party::Json; 
    const JsonNative* Ptr() const noexcept; 
    JsonNative* Ptr() noexcept; 

    constexpr std::size_t kImplSize = 32; 
    constexpr std::size_t kImplAlign = 8; 
    std::aligned_storage_t data_; 
};

The only issue: for each wrapper, we need to specify the size and alignment — let’s make our pimpl template-based with parameters , use some arbitrary values, and add a check in the destructor to ensure everything has been guessed correctly: 

~FastPimpl() noexcept { 
    validate(); 
    Ptr()->~T(); 
}

template 
static void validate() noexcept { 
    static_assert(
        Size == ActualSize, 
        "Size and sizeof(T) mismatch"
    ); 
    static_assert(
        Alignment == ActualAlignment, 
        "Alignment and alignof(T) mismatch"
    ); 
}

Since the destructor T is already defined during processing, this code will be resolved correctly at the compilation stage and will output the required size and alignment values as errors. Thus, at the cost of one additional compilation run, we eliminate dynamic allocation of wrapped classes, hide the API in the .cpp file with the implementation, and also obtain a structure that is more cache-friendly for the processor.

Logging and parsing seemed less impressive and will therefore not be mentioned in this review.

The presentation slides are available at the link: [“C++ Tricks from Taxi”]

Modern techniques for keeping your code DRY, Björn Fahller

In this report, Björn Fahller presents several different ways to combat such stylistic shortcomings as redundant condition checks:

assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);

Familiar? Using several powerful techniques from C++ that have appeared in recent standards, you can elegantly implement the same functionality without any loss of performance. Compare:   

assert(a == any_of(IDLE, CONNECTED, DISCONNECTED));

To handle a variable number of checks, variadic templates and fold expressions come into play. Suppose we want to check the equality of several variables to an element of the enum state_type. The first thing that comes to mind is to write a helper function is_any_of:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

template 
bool is_any_of(state_type s, const Ts& ... ts) { 
    return ((s == ts) || ...); 
}

Such an intermediate result is disappointing. So far, the code does not become any more readable:

assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); 

Non-type template parameters can help improve the situation a bit. With their help, we will transfer the enum elements to the list of template parameters: 

template 
bool is_any_of(state_type t) { 
    return ((t == states) || ...); 
}
	
assert(is_any_of(state)); 

Using auto in a non-type template parameter (C++17) allows this approach to generalize comparisons not only with elements of state_type but also with primitive types that can be used as non-type template parameters:


template 
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

Through such successive improvements, the desired fluent syntax for checks is achieved:


template 
struct any_of : private std::tuple { 
// let's be lazy and inherit the constructors from tuple
        using std::tuple::tuple;
        template 
        bool operator ==(const T& t) const {
                return std::apply(
                        [&t](const auto& ... ts) {
                                return ((ts == t) || ...);
                        },
                        static_cast<const std::tuple&>(*this));
        }
};

template 
any_of(Ts ...) -> any_of;

assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);

In this example, the deduction guide serves to hint the desired template parameters of the structure to the compiler, aware of the argument types of the constructor. 

Next, it gets more interesting. Bjorn teaches how to generalize the resulting code for comparison operators beyond ==, and then for arbitrary operations. Along the way, features such as the no_unique_address attribute (C++20) and template parameters in lambda functions (C++20) are explained through usage examples. (Yes, the syntax of lambdas is now even easier to remember – it's four consecutive pairs of brackets of all sorts.) The final solution using functions as constructor parts really warms my heart, not to mention the expression of tuple in the best traditions of lambda calculus.

In the end, let's not forget to polish it up:

  • Let's remember that lambdas are constexpr for free; 
  • We'll add perfect forwarding and look at its ugly syntax as applied to parameter pack in lambda closures;
  • We'll give the compiler more opportunities for optimizations with conditional noexcept; 
  • We'll take care of clearer error reporting in templates through explicit return values of lambdas. This will force the compiler to perform more checks before actually calling the template function – at the type-checking stage. 

For details, refer to the lecture materials: 

Our impressions

Our first participation in C++ Russia was memorable for its intensity. It felt like C++ Russia was a heartfelt event where the line between learning and lively interaction was almost indistinguishable. Everything, from the speakers' enthusiasm to the contests from event partners, encouraged vibrant discussions. The conference's substantive part, consisting of presentations, covers a broad range of topics, including C++ innovations, practical examples from large projects, and ideological architectural considerations. However, it would be unfair to overlook the social aspect of the event, which helps overcome language barriers concerning not only C++.

We thank the conference organizers for the opportunity to participate in such an event!
You may have seen the organizers' post about the past, present, and future of C++ Russia on the JUG Ru blog..

Thank you for reading, and we hope our recap of the events was helpful!

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster