Блог пользователя EnDeRBeaT

Автор EnDeRBeaT, 2 месяца назад, По-английски

Introduction

Rust is a quite popular programming language with a lot of different takes on how to write good code. Some people use it in competitive programming with varying success, but in general, C++ is the king.

That doesn't mean that we can't take great ideas from this language, and one of Rust's shining jewels is it's macro system.
Describing why it is better than "find and replace" thing we have in C++ is not the topic of this blog. We are here to steal from Rust, and today we will steal one of the coolest utilities for a competitive programmer: dbg! macro!

dbg!

The dbg! macro in Rust has simple functionality: it evaluates the expression, prints it's to stderr and returns it. Last part is very, very useful, and you will see why.

Example:

let a = 2;
let b = dbg!(a * 2) + 1;
//      ^-- prints: [src/main.rs:2:9] a * 2 = 4
assert_eq!(b, 5);

Look, we evaluated a * 2 mid expression and b still became 5. Isn't that cool?

Not cool? Here's a crazier example:

fn factorial(n: u32) -> u32 {
    if dbg!(n <= 1) {
        dbg!(1)
    } else {
        dbg!(n * factorial(n &mdash; 1))
    }
}

dbg!(factorial(4));

Which produces the following output:

[src/main.rs:2:8] n <= 1 = false
[src/main.rs:2:8] n <= 1 = false
[src/main.rs:2:8] n <= 1 = false
[src/main.rs:2:8] n <= 1 = true
[src/main.rs:3:9] 1 = 1
[src/main.rs:7:9] n * factorial(n &mdash; 1) = 2
[src/main.rs:7:9] n * factorial(n &mdash; 1) = 6
[src/main.rs:7:9] n * factorial(n &mdash; 1) = 24
[src/main.rs:9:1] factorial(4) = 24

Now, do you think that it's a great feature? We can print an expression, an expression in another expression, hell, we can even inject into the if statement with it!

By that time, you are probably convinced that you can't live without this function. Or you just want to see how this feature can be implemented in C++. Either way, you won't be disappointed.

Let's steal the macro

On the basic level this function just prints the expression and its value (well it also prints filename, row and column, but that isn't that hard to do), and then returns the value.

It is very easy to obtain expression as a string literal in C++:

#define TOSTRING(expr) #expr

std::cout << TOSTRING(1 123 * 128 01); // prints 1 123 * 128 01

For "printing and returning" part, we can just use a regular template function. So, our basic draft for a debug macro will be this:

template<typename T>
T debugln(std::string_view str, T&& expr) {
    std::cerr << str << " = " << expr << '\n'; // assuming expr is even printable
    return expr;
}
#define dbg(expr) debugln(#expr, expr)

And it is already almost working:

int a = dbg(5); // prints 5 = 5
int b = dbg(a * 2) + 5; // prints a * 2 = 10
std::cout << a << ' ' << b; // prints 5 15

Using dbg in if statements is a 2 symbol fix, we just need to realize that (x) <=> x hence we can just wrap our macro body in parentheses.

template<typename T>
T debugln(std::string_view str, T&& expr) {
    std::cerr << str << " = " << expr << '\n';
    return expr;
}
#define dbg(expr) (debugln(#expr, expr))

int main() {
    int a = dbg(5); // prints 5 = 5
    int b = dbg(a * 2) + 5; // prints a * 2 = 10
    if dbg(a * 2 + 5 == b) { // prints a * 2 + 5 == b = 1
        std::cout << "Macro is working!!"; // prints Macro is working!!
    }
}

Wait, that's all?

For the single value case, yes. But there's another thing about dbg! macro in Rust: it works with multiple values...

let (a, b) = dbg!("The answer to life is", 42); // print
//      ^-- prints: [src/main.rs:1:14] "The answer to life is" = "The answer to life is"
//                  [src/main.rs:1:14] 42 = 42

It prints each argument on a new line and returns a tuple.

I mean, it's probably not too hard to add, right?

WRONG

That's where C++ macro system shines, and by that I mean that here it sucks. By the end of the blog our code will look so disgusting and foul it would make the devil blush.

So let's start!

__VA_OPT__ macro

In C++20 (God bless C++20) we got a new macro called __VA_OPT__.
The use is pretty straightforward: if our macro is variadic (has ... as one of the arguments) and ... is non-empty, __VA_OPT__(content) gets replaced by content, and expands to nothing otherwise.
This is a very useful macro, which will aid us severely. Right now we can use it to make a tuple if we get more than one argument.

#define dbg(head, ...) __VA_OPT__(std::make_tuple)(FOR_EACH(head, __VA_ARGS__))

The change is simple: we added ... in macro definition. If dbg(expr) is called, an evaluation of this expression will be returned. If something like dbg(x, y) is called, __VA_OPT__ will be triggered and we will make a tuple of all values that FOR_EACH makes.

Wait...

What the fuck is FOR_EACH?

FOR_EACH is obviously something we will have to implement. On the high level, this function will replace each argument it was given with something (in case of __VA_ARGS__, it will do something with each argument in it).

So, can we iterate arguments in a C++ macro?

The answer is no*

The asterisk

Well, we kinda can.

Iteration is definitely out of question, but we can try recursion. According to cppreference (link), you can create pseudo recursion using something like the following code:

#define EMPTY
#define SCAN(x)     x
#define EXAMPLE_()  EXAMPLE
#define EXAMPLE(n)  EXAMPLE_ EMPTY ()(n-1) (n)
EXAMPLE(5) // expands to EXAMPLE_()(5 &mdash; 1)(5)
SCAN(SCAN(EXAMPLE(5))) // expands to EXAMPLE_()(5 &mdash; 1 &mdash; 1)(5 &mdash; 1)(5)

How does it work? I am probably not the best person to explain this. Without going in too much details, you can't recurse in macros: When compiler scans the replacement list of a function macro X and it sees the same macro X, it won't replace it.
But once compiler is out of the replacement list of X, it becomes available for replacement once again.

Let's see it on a better example than cppreference gives us, and make a macro that just repeatedly creates 1s.

#define EMPTY
#define SCAN(x)     x
#define ANOTHER_ONE()  ONE
#define ONE() 1 ANOTHER_ONE EMPTY ()()

SCAN(ONE())
// Expands to
1 1 ANOTHER_ONE()()

Let's now expand this step by step (this may be wrong, this feature is very weird, and not properly documented in C++ ISO):

SCAN(ONE()) // ONE() -> 1 ANOTHER_ONE EMPTY ()()
// if we don't have EMPTY, ANOTHER_ONE() would have mapped to ONE disabling further expansion
SCAN(1 ANOTHER_ONE EMPTY ()()) // ONE is not replaceable, EMPTY -> 
SCAN(1 ANOTHER_ONE ()()) // ONE is replaceable, SCAN(1 ANOTHER_ONE ()()) -> 1 ANOTHER_ONE ()()
1 ANOTHER_ONE ()() // SCAN is not replaceable, ANOTHER_ONE() -> ONE
1 ONE() // ONE() -> 1 ANOTHER_ONE EMPTY ()()
1 1 ANOTHER_ONE EMPTY ()() // EMPTY -> 
1 1 ANOTHER_ONE ()() // ANOTHER_ONE is replaceable, but the expansion is already over.

EMPTY macro here is important, without breaking up ANOTHER_ONE and its parentheses, ANOTHER_ONE will be called BEFORE ONE has expanded it's arguments, which will render it unexpandable.

Now with that out of the way, let's build our iteration/recursion.

Gluing everything together

Each SCAN adds only a single iteration, but we can nest macros to get exponentially more scans.

#define SCAN(...) SCAN3(SCAN3(SCAN3(__VA_ARGS__)))
#define SCAN3(...) SCAN2(SCAN2(SCAN2(__VA_ARGS__)))
#define SCAN2(...) SCAN1(SCAN1(SCAN1(__VA_ARGS__)))
#define SCAN1(...) SCAN0(SCAN0(SCAN0(__VA_ARGS__)))
#define SCAN0(...) __VA_ARGS__

Since we are going for multiple arguments, SCAN takes multiple arguments.

Now, what do we do with our findings? In the example, there was an annoying ANOTHER_ONE ()() at the end of recursion, how do we remove it? By using __VA_OPT__!

We obviously halt recursion when we run out of arguments, so we can just write the following code:

#define EMPTY
#define FOR_EACH_ONCE(head, ...) debugln(#head, head) __VA_OPT__(, FOR_EACH_RESTART EMPTY ()(__VA_ARGS__))
// if ... is non-empty __VA_OPT__ will continue the iteration by calling FOR_EACH_RESTART
#define FOR_EACH_RESTART() FOR_EACH_ONCE

Note how we also added , inside __VA_OPT__ in case we have more than one argument.

Our FOR_EACH macro:

#define FOR_EACH(...) SCAN(FOR_EACH_ONCE(__VA_ARGS__))

Straightforward, just put our FOR_EACH_ONCE inside our huge SCAN to start the "recursion".

And with dbg macro we have announced earlier we get:

#define dbg(expr, ...) __VA_OPT__(std::make_tuple) (FOR_EACH(expr, __VA_ARGS__))


int main() {
    int a = dbg(5); 
    int b = dbg(a * 2) + 5;
    dbg(a * 2 + 5, b); 
    if dbg(a * 2 + 5 == b) {
        std::cout << "Macro is working!!";
    }
}

Output:

5 = 5
a * 2 = 10
b = 15
a * 2 + 5 = 15
a * 2 + 5 == b = 1
Macro is working!!

Making it prettier

Well, it's working very well apart from cases when there are nested dbg.

int main() {
    int a = dbg(5); 
    int b = dbg(dbg(a * 2) + 5);
}

// the output is:
// 5 = 5
// a * 2 = 10
// (debugln("a * 2", a * 2)) + 5 = 15

You see the last one? It unwraps the statement which is, of course, ugly, and also not how Rust handles this case.

Sadly there is no way to avoid that because of macro rescanning rules.

However, usually, the cases where you need to nest dbg are cases with a single arguments so we can... unwrap first pass of FOR_EACH!

#define dbg(head, ...) __VA_OPT__(std::make_tuple) \
    (SCAN(debugln(#head, head) __VA_OPT__(, FOR_EACH_RESTART EMPTY ()(__VA_ARGS__))))

Dirty? As fuck. But this is C++ macro tutorial, this is nothing compared to the crimes we have committed so far.

Filename and Line

I have omitted the column because I couldn't find a proper way of it working.

This is pretty unnecessary for a competitive programmer, but it's also pretty educational.

The plan is simple: we just prepend "[filename:line]" string literal to #name in our debugln calls.

Fun fact, you can easily append string literals by just writing them next to each other:

constexpr std::string_view a = "Hello," " World";
static_assert(a == "Hello,World"); // Compiles

We can retrieve the current filename and line by using macros __FILE_NAME__ and __LINE__. Since __LINE__ expands into an integer, we have to stringify it, and since C++ macros are C++ macros, we have to do it through two indirections (because of the same macro expansion rules which let us make dbg prettier).

#define TO_STRING(x) TO_STRING2(x) // __LINE__ evaluates to the integer before going into TO_STRING2
#define TO_STRING2(x) #x // and here it becomes an actual string literal (i love c++)
#define FOR_EACH_ONCE(head, ...) debugln("[" __FILE_NAME__ ":" TO_STRING(__LINE__) "] " #head, head) __VA_OPT__(, FOR_EACH_RESTART EMPTY ()(__VA_ARGS__))

int main() {
    int a = dbg(5);
    int b = dbg(dbg(a * 2) + 5);
}
// Output
// [a.cpp:27] 5 = 5
// [a.cpp:28] a * 2 = 10
// [a.cpp:28] dbg(a * 2) + 5 = 15

Closing thoughts

And here what our monumental efforts and macro hacks resulted into:

trigger warning: c++ macros

While it isn't one-to-one implementation of Rust dbg!, it is pretty close. Should you use it? Your choice. However, if you are in onsite competitions, I highly recommend to at least implement dbg for a single argument. It's just:

#ifdef _DEBUG
    #define dbg(x) (debugln(#x, x))
#elif
    #define dbg(x) (x)
#endif

template<typename T>
T debugln(std::string_view str, T&& expr) {
    std::cerr << str << " = " << expr << '\n';
    return expr;
}

And you will already have 90% of the dbg! power.

constexpr version?

We experienced great pain, because we tried to implement Rust macro using mostly C capabilities (apart from __VA_OPT__). But C++ possesses great compile time computation capabilities which enable us to write more coherent code and mostly as performant as our macro trickery.

However, this is out of topic of this blog, if you want to see dbg! done C++ way, let me know :)

EDIT: Now thinking about it, I think it's not possible to do, because parsing C++ is hard (a<b,c>(d) can be interpreted as a template specialization or as 2 comparisons, which you can't resolve without knowing the semantic meaning of a).

However, I came with thoughts from using this macro.

Man, fuck tuples.

Since we are returning std::tuple, we got a bunch of problems: the arguments are not in order!!

int main() {
    int a = 5; 
    int b = a * 2 + 5;
    dbg(a * 2 + 5, b);
}
// Output:
// b = 15
// a * 2 + 5 = 15

But why? Because in C++ order of argument evaluation is unspecified, and std::make_tuple is indeed a function. Great, what do we do now. Well, we can update our debugln to print all arguments at once and return a tuple, but do we really need to return a tuple? When would that even be useful? Sure in Rust you can do let (a, b) = dbg!(1, 5); but we can't even do that in C++!! So I am going to say bye to tuples.

small change

Apart from fixing the order, going from a tuple to just values enabled us to do a very funny thing:

int mul(int a, int b) {
    return a * b;
}

int main(){
    int c = mul dbg(5 * 2, 6);
}
// Output
// 6 = 6
// 5 * 2 = 10

Ugly? Yes, but this is codeforces, we have seen (and done) worse. Values are again not ordered? Yes, but that's because we print actual arguments for a function, and we can't do anything about that.

What else?

I don't know. Personally, I changed my impl to print everything from a single dbg call on a single line. Some people in the comments also added colored print, which is neat. Your choice. Have fun!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +184
  • Проголосовать: не нравится

Автор EnDeRBeaT, 8 месяцев назад, По-английски

Hi, CodeForces!

For the past 2 months I have been working on a Graph Debugging tool, which allows you to draw graphs like these with just TWO lines of code:

This is similar to print debugging (when you got a crash somewhere, so you repeatedly recompile the code with cout << "penis\n"; to see where it failed), but instead it creates a window with the graph, which you can change however you want.

It is also a marginally better tool than graph editor from CSAcademy in my opinion, so I would love you guys to try it out.

This is not compatible with MacOS because Apple deprecated OpenGL (and even if it wasn't, it would've been kinda annoying to use)

Building

First, build the code from the repo:
https://github.com/EnDeRGeaT/graph-debugger
If you write in Visual Studio (not Visual Studio Code), follow "For MSVC" part.
If you write in anything else, you probably should follow "For GCC/Clang".

Having competitive programming in mind, I suggest you to not really bother and copy .a (or .lib) files in your compiler's library folder. You can do the same thing with include files, however I chose a different path.

Using

Almost everything is written in github page, but anyway.

You can create a Graph from a list of edges:

Code

Or you can create a Graph from your adjacency list:

Code

Or you can create it if it's a C array:

Code

if your representation is 1-indexed, you can add a part of it!

Code

Weighted graph? Not a problem!

Code

And the list goes on!

To get help in moving around in the actual window, just press H. Hints will be outputted in a console (via std::cerr)

You can also create multiple instances of Graph class, and move between them with left/right arrow.

Very important note: visualize() DOES NOT STOP THE EXECUTION. Hence your main code can continue working. It will stop the execution when the last Graph instance is about to get destroyed (and will write so in the console).

There are also ways to visualize with highlighted edges, and there are some basic algos in this graph (they are bad because i was writing them for my discrete math assignments). But that is not that important point for use.

Another important node: In order to not rewrite any code (which kinda defeats the point of this tool), it does everything in a different thread, which is not very correct to do. So, if you have any issues (like window hanging, or a crash), see Issues in the github repo.

Caveats

Graph drawing is hard. Really. I didn't know what I got myself into 2 months ago.
Here are some issues with it:

  • Directed graphs are not really supported (it is not that hard to add support though)
  • Self-loops are also not supported (it is also kinda hard to add support for it. Multi edges are supported though!)
  • Prettifier for the graph is not very good, it is giga slow, and also ugly for dense graphs. If you want, you can help me with writing a better one.

Future

This was my first project that didn't involve competitive programming and it was very hard. Since it is my first project, the code quality is also very subpar! The library can be riddled with bugs due to it.

I can maintain it for some time, but it all depends on whether CP community will like what I did.

So, I hope you guys like this tool, you can ask any questions here :)

Полный текст и комментарии »

  • Проголосовать: нравится
  • +208
  • Проголосовать: не нравится

Автор EnDeRBeaT, история, 18 месяцев назад, По-английски

Codeforces Round #880 has just finished, and here are the first solvers of problem E in div2 and div1 respectfully:

This guy stands right next to Um_nik, his name translates to "I've hung out with Um_nik's wife", and he's cyan(as we know, a 1/1000 of Um_nik).

This was my first div1 contest, I proudly solved 1 problem, and was sad about it, but such a great coincidence has elevated my mood significantly.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +363
  • Проголосовать: не нравится

Автор EnDeRBeaT, история, 2 года назад, По-английски

Hello, today in CodeTON round #3, one of the most epic moments in cf history has happened. It's about tourist.

Did he finally cross 4000 rating? No.

Today, he got owned by his archenemy, python gigachad conqueror_of_tourist.

Congratulations to conqueror_of_tourist. Good job, king!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +265
  • Проголосовать: не нравится