Having fun with modern C++

Having fun with modern C++

Recent versions of the C++ language (C++20 and C++23) may allow you to change drastically how you program in C++. I want to provide some fun examples.

Thanks to the integration of the features from the popular fmt library, it is much easier to format strings elegantly in C++. In turn the fmt library was inspired by the work done in languages like Python.

Suppose that you have a vector of integers and you want to print its content:

std::vector<int> v = {1, 2, 3, 4, 5};
std::println("{}", v);        

Suppose you want it to be centered in a line of 40 characters, with underscore characters around it:

    std::vector<int> v = {1, 2, 3, 4, 5};
    std::println("{:_^40}", v);
    // ____________[1, 2, 3, 4, 5]_____________        

Maybe you want to print it in reverse order?

    std::println("{}", v | std::views::reverse);        

Want to print its maximum?

    std::println("{}", std::ranges::max(v));        

What about printing my name and the current time, and my name again? We do not want to repeat the name twice in the parameters, so let us use indexed printing parameters:

  std::string_view name = "Daniel"sv;
  std::println("Hello, {0} today is {1:%Y-%m-%d %X}, good day {0}!", name,
               std::chrono::system_clock::now());
   // Hello, Daniel today is 2024-11-02 00:02:17, good day Daniel!        

Let say you want to print different values, each on their own line, justified to the right, with hyphens padding the left. For this purpose, we want to have template function which can take as many parameters as you want, and we want to do the same operation on each parameter. We can achieve this result with a fold, like so:

void print(auto ...args) {
  (std::println("{:->50}", args), ...);
}

void prints() {
  print("a", 1, "b", 2, "c", 3);
}

//-------------------------------------------------a
//-------------------------------------------------1
//-------------------------------------------------b
//-------------------------------------------------2
//-------------------------------------------------c
//-------------------------------------------------3        

It looks a bit mysterious, but folds are great if you want to have functions that elegantly take several parameters.

Suppose you want a function which takes two integer-like values, and returns their quotient… with the added trick that if the divisor is zero, you return a string as an error. We want it to work with any integer type (but only integer types). We would rather avoid exceptions. Then `std::expected is perfect for the task. It is effectively a value/error pair conveniently packaged. The following code illustrates the idea… and I have added a ‘test’ function to show you how it might used.

template <std::integral number>
std::expected<number, std::string> divide(number a, number b) {
  if (b == 0) {
    return std::unexpected("Division by zero");
  }
  return a / b;
}

void test(auto x, auto y) {
  if (auto result = divide(x, y); result) {
    std::println("Result: {}", result.value());
  } else {
    std::println(stderr, "Error: {}", result.error());
  }
}        

We wrote a fast URL parser called ada, and it returns parsed URL in an std::expected structure. This allows us to avoid the complexity of exceptions. The fast JSON library simdjson has a custom return type that follows the same idea.

Let us say that you want to print the integer 4 in binary (100) and know how many trailing 0 bits there are (the answer is 2):

  std::println("{:b} {}", 4u, std::countr_zero(4u));
  // 100 2        

Suppose you want to rotate left the bits in an integer by 4:

  std::println("{:x}", std::rotl(0xf0f0f0f0u, 4));
  // f0f0f0f        

Suppose you want to know what the floating point number 1.0 looks like as a 64-bit word:

 std::println("{:b}", std::bit_cast<uint64_t>(1.0));
  // 11111111110000000000000000000000000000000000000000000000000000        

Suppose you want to print the size of a file in bytes?

  std::println("{} bytes", std::filesystem::file_size("demo.cpp"))        

Suppose you want to write to a log while including the exact position in the source code where the log was called?

  auto log = [](std::string_view message, std::source_location loc) {
    std::println("{} {}:{} function name: {}", message, loc.file_name(),
                 loc.line(), loc.function_name());
  };

  log("problem", std::source_location::current());        

There are many more cool features in recent version of C++. If you want to learn more, I recommend recent books by Marius Bancila. He is a great technical writer.

Juan Cruz Viotti

Founder | O'Reilly author | Consultant | JSON Schema TSC

3 周

Great times to be a C++ developer!

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了