Jacksonville’18 ISO C++ Report

This week the ISO C++ committee met in Jacksonville, Florida. This is the first meeting of this year. We will meet again in Summer (Rapperswil, Switzerland) and Fall (San Diego, California).

As you may well know the committee is working in what it will be C++20 (and deciding what could end up delayed for C++23).

Let me give a summary of some of the things we decided today in plenary. As always we decided to approve the resolutions solving a number of defect reports both in the library and in the language. Usually this are corner cases and leave to others (or to me in the future) to explain them.

In this post you will find information only about the language and the library for C`20. I defer reporting on TS (technical specifications) to a separate post.

C++20 Language

Language support for empty objects

A new attribute [[no_unique_address]] has been added to indicate that a unique address is not required for a non static data member. Such data member can the share its address with another object if it could have zero size (as in the empty base optimization) and both have distinct types:

template<typename Key, typename Value, typename Hash, typename Pred, typename Allocator>
class hash_map {
  [[no_unique_address]] Hash hasher;
  [[no_unique_address]] Pred pred;
  [[no_unique_address]] Allocator alloc;
  Bucket *buckets;
  // ...
public:
  // ...
};

Then hasher, pred, and alloc may have the same address than buckets (if they have zero size).

Relaxed support for range-based for loops

This solves corner cases for range-based for. Surprise or not, today you cannot write a range-based for loop over data coming for an input stream without writing some adaptor class. The problem is that seekdir::end is found and this makes impossible to properly find begin() and end().

This is an example of code that does not work in C++17, but will work in C++20.

struct X : std::stringstream { /*...*/ };

std::istream_iterator<char> begin(X& x) {
    return std::istream_iterator<char>(x);
}

std::istream_iterator<char> end(X& x) {
    return std::istream_iterator<char>();
}

int main() {
    X x;
    for (auto && i : x) {
      // ...	      
    }
}

Allow structured bindings to accessible members

C++17 introduced structured bindings making it possible to bind only to public data members. C++20 exteds this to the case of accessible data members. This solves problems with using them in friend functions.

struct A { 
  friend void f(); 
private: 
  int x; 
}; 

struct B : private A {
  friend void g();
};

void f() { 
  A a; 
  auto x = a.x; // OK 
  auto [y] = a; // Allowed by this change 
}

void f() {
  B b;
  auto x = b.x; // OK
  auto [y] = b; // Allowed by this change

This change also allows to class access its own data members through structured bindings:

class C {
  int x;
  void f(const C & c) {
    auto [x] = c; // Allowed by this change
  }
}

Note that this has been approved for C++20, but has been also marked as a defect report for C++17.

Relaxing the structured bindings customization point finding rules

This change solves another problem with structured bindings and the get() function that affects to classes that have a non templated get() member function. Note that this is the case of smart pointers.

With this change “if a member get() is found, it must be a member template to be used with a structured binding. If a get() that is not a template is found, that member is not used, and the lookup continues to try and find an ADL get<>().

Down with typename!

You may have got tired of writing typename in places where you think the compiler could do it for you. Have you been there?

Let me show an example from the new text in the standard:

template<class T> T::R f(); // OK, return type of a function declaration at global scope

template<class T> void f(T::R); // Ill-formed (no diagnostic required), attempt to declare a void variable template

template<class T> struct S {
  using Ptr = PtrTraits<T>::Ptr; // OK, in a defining-type-id
  T::R f(T::P p) { // OK, class scope
    return static_cast<T::R>(p); // OK, type-id of a static_cast
  }
  auto g() -> S<T*>::Ptr;  // OK, trailing-return-type
};

template<typename T> void f() {
  void (*pf)(T::X); // Variable pf of type void* initialized with T::X
  void g(T::X); // Error: T::X at block scope does not denote a type
                // (attempt to declare a void variable)
}

Allow pack expansion in lambda init-capture

You may have wanted to pass a variadic argument to lambda by capturing it by move. Now you can!

template <typename ... Args>
void f(Args ... args) {
  auto f = [...x = std::move(args)]() { return g(x...); };
  f();
}

Attributes for likely and unlikely

This is something that different compilers have been doing in a non-portable way. You can now mark which branch is likely to be executed to give that information to your optimizer.

if (a>b) [[likely]] {
  do_something();
}
else {
  do_other();
}

You can also optimize the common case in nested branches:

if (a>b) {
  if (c>d) [[likely]] {
    do_something();
  }
}

Or tell the compiler that a loop usually is not executed:

while (a>0) {
  [[unlikely]] g();
}

Or mark the most common case in a switch:

switch (x) {
  case one:
    f();
    break;
  case two:
    [[likely]] g();
    break;
  default:
    h();
}

Adding symmetry to operator <=> (aka spaceship operator)

You may be aware of operator<=> which allows you to define a single operator for all comparisons in a class.

Currently, if the language finds an expression like b<a (where b and a may be of different types), the following operations are matched:

  1. operator<(b,a) and b.operator<(a)
  2. operator<=>(b,a) < 0 and b.operator<=>(a) < 0
  3. 0 < operator<=>(a,b) and 0 < a.operator<=>(b)

This allows to provide a single definition of operator<=>. However, if the language finds an expression like b<=>a (even if a<=>b exists), this rule doesn’t apply. This can be seen as a lack of symmetry for operator <=> which has been corrected now.

C++20 library

A <version> header

This is a new header file with implementation-dependent version information. Just to clarify the information that you may find there might be different in each implementation and it will be related to your own vendor. Consequently, you do not need to go there to get any standard information.

In fact, there is no standard symbol defined in that header.

Comparing Unordered Containers

Current comparison of unordered containers is undefined unless Hash and Pred have exactly the same behavior for both containers. This implies that to different hash functions (for example, using different seeds) might lead to containers being different.

Hash has now being removed from the undefined behavior definition.

Extending <chrono> to Calendars and Time Zones

This is (to my view) a very important component of C++20 standard library. I am not going into details here as you may have a look to an implementation at https://github.com/HowardHinnant/date.

string::reserve Should Not Shrink

Until C++17 std::reserve(sz) might reallocate if the new size is smaller than the capacity. This brings a number of problems:

  • It may be a performance penalty.
  • It may cause portability problems.
  • Is inconsistent with behavior of std::vector.
  • Complicates generic code that wants to handle uniformly vector and string.

In C++20 std::reserve(sz) will never perform a shrink_to_fit(sz).

Thou Shalt Not Specialize std Function Templates!

Specializing entities from std has been strongly restricted:

  • Specializing function templates from the std namespace is not allowed.
  • Taking the address of a standard library function is not allowed.

Manipulators for C++ Synchronized Buffered Ostream

Three new manipulators have been added to be used with synchronized ostreams (basic_osyncstream):

  • emit_on_flush: Makes flushes to apply immediately.
  • noemit_on_flush: Make flushes to be postponed until an emit.
  • flush_emit: Flush all remaining information.

span: bounds-safe views for sequences of objects

Class span offers a view over a sequence that may live in an array or vector, but does not own such elements. Consequently, span does not perform any memory allocation or deallocation.

void f(span<int> s) {
  //...
}

void g() {
  int v[100];
  span<int,100> s{v};
  f(s);
}

Constexpr iterator requirements

Defines the concept of a constexpr iterator, which is an iterator whose operations are constexpr. This will allow that those containers providing constexpr iterators (e.g. std::array) may be used in constexpr functions.

Esta entrada fue publicada en cpp-posts, misc, Sin categoría, Trip Reports. Guarda el enlace permanente.