C++ Russia: how it was

If at the beginning of the play you say that there is C++ code hanging on the wall, by the end it should definitely shoot you in the foot.

Bjarne Stroustrup

From October 31st to November 1st, the C++ Russia Piter conference was held in St. Petersburg - one of the largest programming conferences in Russia, organized by JUG Ru Group. Guest speakers include members of the C++ Standards Committee, speakers at CppCon, authors of O'Reilly books, and maintainers of projects such as LLVM, libc++, and Boost. The conference is aimed at experienced C++ developers who want to deepen their expertise and exchange experiences in live communication. Very pleasant discounts are provided to students, graduate students and university professors.

The Moscow edition of the conference can be visited as early as April next year, but for now, our students will tell what interesting things they learned at the last event. 

C++ Russia: how it was

Photos of conference album

About Us

Two students from the Higher School of Economics - St. Petersburg worked on this post:

  • Liza Vasilenko is a 4th year undergraduate student studying Programming Languages ​​as part of the Applied Mathematics and Informatics program. Having got acquainted with the C ++ language in the first year of the university, she subsequently gained experience working with it during internships in the industry. Passion for programming languages ​​in general and functional programming in particular left its mark on the choice of reports at the conference.
  • Danya Smirnov is a 1st year student of the Master's program in Programming and Data Analysis. Even at school, I wrote Olympiad problems in C ++, and then it somehow happened that the language constantly surfaced in educational activities and eventually became the main worker. I decided to participate in the conference in order to improve my knowledge and learn about new opportunities.

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

Conference structure

  • Reports

Over the course of two days, experts read 30 talks covering many hot topics: ingenious applications of language features for solving applied problems, upcoming language updates due to the new standard, C ++ design trade-offs and precautions when dealing with their consequences, examples of interesting project architecture, as well as some under the hood details of the language infrastructure. At the same time, 3 performances were held, most often two in Russian and one in English.

  • Discussion zones

After the speech, all unasked questions and unfinished discussions were transferred to specially designated areas for communication with speakers, equipped with marker boards. A good way to pass the break between performances for a pleasant conversation.

  • Lightning Talks and informal discussions

If you want to make a short report, you can sign up on the whiteboard for the evening Lightning Talk and get five minutes of time to talk about anything on the topic of the conference. For example, a quick introduction to C++ sanitizers (new to some) or a story about a bug in sine wave generation that can only be heard but not seen.

Another format is the panel discussion “With a heart-to-heart committee”. On the stage are some members of the standards committee, on the projector is a fireplace (officially - to create a spiritual atmosphere, but the reason "because EVERYTHING IS ON FIRE" seems more fun), questions - about the standard and the general vision of C ++, without heated technical discussions and holivars. It turned out that the committee also includes living people who may not be completely sure of something or may not know something.

For lovers of holivars, the third event remained on the case - the BOF session “Go against C ++”. We take a Go lover, a C ++ lover, before the session starts, they prepare 100500 slides together on a topic (like problems with packages in C ++ or lack of generics in Go), and then they have a lively discussion among themselves and with the audience, and the audience tries to understand two points of view at once . If a holivar starts out of business, the moderator intervenes and reconciles the parties. This format is addictive: a few hours after the start, only half of the slides were completed. The end had to be rushed.

  • Partner stands

The conference partners were presented in the halls - at the stands they talked about current projects, offered internships and employment, held quizzes and small competitions, and also raffled off nice prizes. At the same time, some companies even offered to go through the initial stages of interviews, which can be useful for those who came not only to listen to reports.

Technical details of reports

We listened to the reports both days. Sometimes it was difficult to choose one report from the parallel ones - we agreed to share and share the knowledge gained during breaks. Even so, there seems to be a lot left out. Here we would like to talk about the content of some reports that seemed to us the most interesting

Exceptions in C++ Through the Prism of Compiler Optimizations, Roman Rusyaev

C++ Russia: how it was
slide from presentations

As the name implies, Roman discussed working with exceptions using LLVM as an example. At the same time, for those who do not use Clang in their work, the report can still give some idea of ​​​​how the code can potentially be optimized. This is so because the developers of compilers and the corresponding standard libraries communicate with each other and many successful solutions can coincide.

So, to handle an exception, you need to do a lot of things: call the handling code (if any) or release resources at the current level and unwind the stack higher. All this leads to the fact that the compiler adds additional instructions for potentially throwing exception calls. Therefore, if the exception is not actually thrown, the program will still perform unnecessary actions. In order to somehow reduce overhead, LLVM has several heuristics for determining situations where exception handling code does not need to be added or the number of “extra” instructions can be reduced.

The speaker considers about a dozen of them and shows both situations where they help speed up the execution of the program, and those where these methods are not applicable.

Thus, Roman Rusyaev leads listeners to the conclusion that code containing work with exceptions cannot always be executed with zero overhead, and gives the following advice:

  • when developing libraries, it is worth abandoning exceptions in principle;
  • if exceptions are still needed, then whenever possible, it is worth adding noexcept (and const) modifiers everywhere so that the compiler can optimize as much as possible.

In general, the speaker confirmed the view that exceptions are best kept to a minimum or not at all.

The presentation slides are available at: [“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 presentations

One of the many reports of this conference, dedicated to the innovations of C++20, was remembered not only by a colorful presentation, but also by a clear indication of the existing problems with the logic of processing collections (loop for, callbacks).

Adi Shavit highlights the following: the currently available methods traverse the entire collection and at the same time do not give access to some internal intermediate state (or they do in the case of callbacks, but with a lot of unpleasant side effects, such as the same Callback Hell). It would seem that there are iterators, but everything is not so smooth with them either: there are no common entry and exit points (begin → end versus rbegin → rend, and so on), it is not clear how much we will iterate in general? Starting with C++20, these problems are solved!

First option: ranges. By wrapping over iterators, we get a common interface for the start and end of an iteration, and we also get the ability to compose. All this makes it easy to build full-fledged data processing pipelines. But not everything is so smooth: part of the calculation logic is inside the implementation of a particular iterator, which can complicate the code for perception and debugging.

C++ Russia: how it was
slide from presentations

Well, for this case, C++20 added coroutines (functions whose behavior is similar to generators in Python): execution can be delayed by returning some current value while maintaining an intermediate state. Thus, we achieve not only work with data as it appears, but also encapsulate all the logic inside a specific coroutine.

But there is a fly in the ointment: at the moment they are only partially supported by existing compilers, and they are also not implemented as neatly as we would like: for example, you should not use links and temporary objects in coroutines yet. Plus, there are some restrictions on what coroutines can be, and constexpr functions, constructors/destructors, and main are not included in this list.

Thus, coroutines solve a significant part of the problems with the simplicity of data processing logic, but their current implementations need to be improved.

Materials:

C++ tricks from Yandex.Taxi, Anton Polukhin

In my professional activity, sometimes I have to implement purely auxiliary things: a wrapper between the internal interface and the API of some library, logging or parsing. In this case, there is usually no need for any additional optimization. But what if these components are used in some of the most popular services on the Runet? In such a situation, you will have to process terabytes per hour of logs alone! Then every millisecond counts, and therefore you have to resort to various tricks - Anton Polukhin talked about them.

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

#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, you first want to get rid of the header files of external libraries - this will compile faster, and you can protect yourself from possible name conflicts and other similar errors. 

Okay, moved the #include to the .cpp file: we need a forward-declaration of the wrapped API, as well as a std::unique_ptr. Now we have dynamic allocations and other nasty things like data piling up and reduced guarantees. With all this, std::aligned_storage can help. 

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<kImplSize, kImplAlign> data_; 
};

The only problem: you need to set the size and alignment for each wrapper - let's make our pimpl template with parameters , use it with some arbitrary values ​​and add a check to the destructor that we guessed everything: 

~FastPimpl() noexcept { 
    validate<sizeof(T), alignof(T)>(); 
    Ptr()->~T(); 
}

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

Since T is already defined when processing the destructor, this code will be parsed correctly and at the compilation stage it will display the necessary size and alignment values ​​that need to be entered as errors. Thus, at the cost of one additional compilation run, we get rid of the dynamic allocation of wrapped classes, hide the API in the .cpp file with the implementation, and also get a construction more suitable for caching by the processor.

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

The presentation slides are available at: ["C++ Tricks from Taxi"]

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

In this talk, Björn Fahller shows several different ways to deal with the stylistic problem of repeated condition checks:

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

Familiar? By using some of the powerful C++ techniques that have appeared in recent standards, you can gracefully implement the same functionality without any performance penalty. Compare:   

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

To handle a non-fixed number of checks, it is immediately asked to use variadic templates and fold expressions. Let's say we want to check if several variables are equal to an enum's state_type element. The first thing that comes to mind is to write an is_any_of helper function:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

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

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

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

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

template <state_type ... states>
bool is_any_of(state_type t) { 
    return ((t == states) | ...); 
}
	
assert(is_any_of<IDLE, DISCONNECTING, DISCONNECTED>(state)); 

With the use of auto in a non-type template parameter (C++17), the approach simply generalizes to comparisons not only with state_type elements, but also with primitive types that can be used as non-type template parameters:


template <auto ... alternatives, typename T>
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

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


template <class ... Ts>
struct any_of : private std::tuple<Ts ...> { 
// поленимся и унаследуем конструкторы от tuple 
        using std::tuple<Ts ...>::tuple;
        template <typename T>
        bool operator ==(const T& t) const {
                return std::apply(
                        [&t](const auto& ... ts) {
                                return ((ts == t) || ...);
                        },
                        static_cast<const std::tuple<Ts ...>&>(*this));
        }
};

template <class ... Ts>
any_of(Ts ...) -> any_of<Ts ... >;
 
assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);

In this example, the deduction guide is used to hint the desired struct template parameters to the compiler, which knows the types of the constructor arguments. 

Further - more interesting. Bjorn teaches how to generalize the resulting code to comparison operators other than ==, and then to arbitrary operations. Along the way, using an example of use, such features as the no_unique_address attribute (C++20) and template parameters in lambda functions (C++20) are explained. (Yes, now the lambda syntax is even easier to remember - it's four consecutive pairs of parentheses of all sorts.) The final solution using functions as constructor parts is very warm for me personally, not to mention the tuple expression in the best traditions of lambda calculus.

At the end, do not forget to polish:

  • Recall that lambdas are constexpr for free; 
  • Let's add perfect forwarding and look at its ugly syntax in relation to the parameter pack in the lambda closure;
  • Let's give the compiler more room for optimizations with conditional noexcept; 
  • Let's take care of more understandable error output in templates thanks to explicit lambda return values. This will force the compiler to do more checks before actually calling the template function - at the type checking stage. 

For details, see the lecture materials: 

Our impressions

Our first participation in C++ Russia was memorable for its intensity. I got the impression of C++ Russia as a spiritual event, where the line between learning and live communication is almost invisible. Everything, from the mood of the speakers to competitions from the partners of the event, is conducive to heated discussions. The content of the conference, which consists of reports, covers a fairly wide range of topics including C ++ innovations, examples from the practice of large projects and ideological architectural considerations. But it would be unfair to deprive attention of the social component of the event, which contributes to overcoming language barriers in relation not only to C ++.

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

Thanks for reading, and we hope you found our recap helpful!

Source: habr.com

Add a comment