Understanding Major Upcoming Updates in C++: Core Language of C++20

Selected from modernescpp

Author:JP Tech et al.

Compiled by Machine Heart

Contributors: Panda, Du Wei

C++20 (C++ Programming Language Standard 2020 Edition) will be a significant update for the C++ language, introducing a wealth of new features.C++ developer Rainer Grimm introduces the new features of C++20 through a series of blog posts.Currently, this series has been updated with two articles, and this is the second one, focusing on the core language of C++20 (including some new operators and directives).

Core Language of C++20 A previous blog provided an overview of the concepts, scope, coroutines, and modules of C++20; now we begin to introduce its core language.Understanding Major Upcoming Updates in C++: Core Language of C++20Three-Way Comparison Operator <=> The three-way comparison operator <=> is commonly known as the spaceship operator.The spaceship operator can determine which of two values A and B is greater, smaller, or equal. The compiler can automatically generate the three-way comparison operator.You just need to politely request it with default.In this case, you will receive all six comparison operators:==, !=, <, <=, >, >=.

#include <compare>
struct MyInt {
  int value;
  MyInt(int value): value{value} { }
  auto operator<=>(const MyInt&) const = default;
};

The default <=> performs a lexicographical comparison, using non-static elements in the order of declaration from left to right starting from the base class.Microsoft’s blog has some quite complex and detailed examples:https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/

struct Basics {
  int i;
  char c;
  float f;
  double d;
  auto operator<=>(const Basics&) const = default;
};

struct Arrays {
  int ai[1];
  char ac[2];
  float af[3];
  double ad[2][2];
  auto operator<=>(const Arrays&) const = default;
};

struct Bases : Basics, Arrays {
  auto operator<=>(const Bases&) const = default;
};

int main() {
  constexpr Bases a = { { 0, 'c', 1.f, 1. },
                        { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
  constexpr Bases b = { { 0, 'c', 1.f, 1. },
                        { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
  static_assert(a == b);
  static_assert(!(a != b));
  static_assert(!(a < b));
  static_assert(a <= b);
  static_assert(!(a > b));
  static_assert(a >= b);
}

I believe that the most complex part of this code snippet is not the spaceship operator but using aggregate initialization to achieve the initialization of Bases.Aggregate initialization essentially means that if all elements are public, you can directly initialize the elements of class types (class, struct, or union).In this case, you can use a braced-initialization-list as shown in the example.Well, this has indeed been simplified; see:https://en.cppreference.com/w/cpp/language/aggregate_initialization Using String Literals as Template Parameters Before C++20, you could not use strings as non-type template parameters.With C++20, you can do this.We can use them in the standard-defined basic_fixed_string, which has a constexpr constructor.This constexpr constructor can instantiate the fixed string at compile time.

template<std::basic_fixed_string T>
class Foo {
    static constexpr char const* Name = T;
public:
    void hello() const;
};

int main() {
    Foo<"Hello!"> foo;
    foo.hello();
}

constexpr Virtual FunctionsDue to the dynamic type being unknown, it was not possible to call virtual functions in a constant expression.This limitation will be lifted in C++20.Designated InitializersI will first talk about aggregate initialization.Here is a simple example:

// aggregateInitialisation.cpp

#include <iostream>

struct Point2D{
    int x;
    int y;
};

class Point3D{
public:
    int x;
    int y;
    int z;
};

int main(){

    std::cout << std::endl;

    Point2D point2D {1, 2};
    Point3D point3D {1, 2, 3};

    std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
    std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;

    std::cout << std::endl;
}

I believe there is no need to explain this program.Take a look at the output of this program:Understanding Major Upcoming Updates in C++: Core Language of C++20Explicit is better than implicit.Let’s see what this means.The initialization in the program aggregateInitialisation.cpp is very prone to error because you might write the parameters of this constructor in reverse, and you would never be able to detect it.Designated initializers from C99 can shine here.

// designatedInitializer.cpp

#include <iostream>

struct Point2D{
    int x;
    int y;
};

class Point3D{
public:
    int x;
    int y;
    int z;
};

int main(){

    std::cout << std::endl;

    Point2D point2D {.x = 1, .y = 2};
    // Point2D point2d {.y = 2, .x = 1};         // (1) error
    Point3D point3D {.x = 1, .y = 2, .z = 2};   
    // Point3D point3D {.x = 1, .z = 2}          // (2)  {1, 0, 2}


    std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
    std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;

    std::cout << std::endl;
}

The parameters of instances Point2D and Point3D can be seen from their names.The output of this program is equivalent to that of the program aggregateInitialisation.cpp.The lines with comments (1) and (2) are quite interesting.Line (1) will report an error because the order of the designated initializers does not match the order of their declarations.In line (2), the designated initializer for y is missing.In this case, y will be initialized to 0, similar to using a braced-initialization-list {1, 0, 3}. Various Improvements to Lambdas C++20 also brings many improvements to lambdas. If you want to understand the details of the changes, please refer to Bartek’s blog:https://www.bfilipek.com/2019/02/lambdas-story-part1.html, which discusses improvements to lambdas in C++17 and C++20.In summary, we will see two interesting changes.

  • Allow [=, this] as lambda capture and deprecate the implicit capture with [=]

struct Lambda {
    auto foo() {
        return [=] { std::cout << s << std::endl; };
    }

    std::string s;
};

struct LambdaCpp20 {
    auto foo() {
        return [=, this] { std::cout << s << std::endl; };
    }

    std::string s;
};

In C++20, using implicit [=] capture in a struct lambda will issue a deprecation warning.If you explicitly obtain it by copying [=, this], you will not receive a deprecation warning in C++20.

  • Template Lambdas

You may wonder, like I did, why we need template lambdas?When you write a generic lambda in C++14 as [](auto x){ return x; }, the compiler automatically generates a templated call operator to create a class:

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

Sometimes, you want to define a lambda that only works for a specific type (like std::vector).Now, template lambdas can help us achieve this.You can use concepts instead of type parameters:

auto foo = []<typename T>(std::vector<T> const&amp; vec) { 
        // do vector specific stuff
    };

New Attributes: [[likely]] and [[unlikely]] C++20 introduces two new attributes: [[likely]] and [[unlikely]].These attributes allow you to provide hints to the optimizer:the execution path is more likely or less likely.

for(size_t i=0; i < v.size(); ++i){
  if (unlikely(v[i] < 0)) sum -= sqrt(-v[i]);
  else sum += sqrt(v[i]);
}

New Directives consteval and constinit The new directive consteval creates an immediate function.For an immediate function, every call to the function must produce a compile-time constant expression.Immediate functions are implicitly constexpr functions.

consteval int sqr(int n) {
  return n*n;
}
constexpr int r = sqr(100);  // OK

int x = 100;
int r2 = sqr(x);             // Error

Because x is not a constant expression, the final assignment will produce an error.Thus, sqr(x) will not be executed at compile time. constinit ensures that variables with static storage duration are initialized at compile time.Static storage duration means that the object is allocated at the start of the program and deallocated at the end of the program.Objects declared in the namespace scope (global objects) and those declared as static or extern have static storage duration. std::source_locationC++11 has two macros __LINE__ and __FILE__ to get information about the line and file of the code.In C++20, the class source_location can provide information about the source code’s filename, line number, column number, and function name.The following example from cppreference.com demonstrates the first use case:

#include <iostream>
#include <string_view>
#include <source_location>

void log(std::string_view message,
         const std::source_location&amp; location = std::source_location::current())
{
    std::cout << "info:"
              << location.file_name() << ":"
              << location.line() << " "
              << message << '\n';
}

int main()
{
    log("Hello world!");  // info:main.cpp:15 Hello world!
}

Original link:https://www.modernescpp.com/index.php/c-20-the-core-languageThis article is compiled by Machine Heart, please contact this public account for authorization to reprint✄————————————————Join Machine Heart (full-time reporter/intern):[email protected]Submission or seeking coverage: content@jiqizhixin.comAdvertising & business cooperation:[email protected]

Leave a Comment