Where will Evolution lead C++17?

published at 08.11.2014 14:35 by Jens Weller

This is the third part in my series about the proposals for the current C++ committee meeting in Urbana. This time its all about the subgroup Evolution, which has the most papers, so this is only the first part. The previous parts were about concurrency, and Part 2 about core, networking, models and undefined behavior.

Evolution

This proposal was already mentioned in the previous part, in Reflection, where the problem of defaulted comparison operators was solved with reflection and std::tie. This proposal wants to make it legal, to use = default; on all comparison operators (>,<,==,!=, ...). Those operators still should be implemented as a friend, as an example shows:

class Thing { int a, b; public: // ... friend bool operator<(const Thing&, const Thing&) = default; friend bool operator>(const Thing&, const Thing&) = default; friend bool operator<=(const Thing&, const Thing&) = default; friend bool operator>=(const Thing&, const Thing&) = default; };

As already proposed for ranged-for, this proposal seeks to make auto&& the default type, if no type is given, it also wants to introduce or take the syntax of a ranged-for to be legal in other statements, such as if:

std::weak_ptr<foo> wp; if(x: wp.lock())

This a dramatic change to C++, and not everybody is going to like it, especially the compiler builders will have to deal with how far this spreads. Yet, if next-generation ranged-for finds approval by the committee, it would make sense to allow the same syntax in other C++ controlstructures such as if,while, for etc.

This paper is a counter to N4074, it argues that the proposed change, to make let return {expr} be explicit is wrong, as explicit should never be implicit. More details in the paper, the authors even show an example where N4074 would lead to undefined behavior.

The authors provide a very good abstract:

With enough care we can build libraries that are essentially defect[-]free, but even the best library may fail catastrophically when misused. Runtime contract validation, the practice of checking functions' preconditions when they are called, helps discover misuse in early testing, speeding development and making software more robust. Extending support for contract validation to phases of development beyond early testing would yield substantial further benefits.

Again, the authors have provided a good abstract:

Inline functions have favorable behavior for interfaces which cannot be exposed as objects. Often, users are encouraged to use them to wrap global variables, despite unnatural boilerplate. Other workarounds include class static data members, enumerators, macros, and variable templates, all with awkward syntax or downsides and limited applicability. This proposal defines the inline specifier on variable definitions to indicate semantics similar to inline function evaluation and linkage. More generally, this produces a facility for named values, or variables without persistence, which may supersede or complement the various workarounds.

The current definition for trivially copyable types has a flaw: it also counts for non accessible/deleted copy/move constructors and assignment operators. A trivially copyable object could also be copied by using std::memcpy, which is often better in performance, especially when you have an array of trivially copyable objects. Yet, with a deleted/inaccessible copy/move constructor or assignment operator, it is not sane to do this. The authors propose to update the wording in the standard, and improve the trait std::is_trivially_copyable to return false for inaccessible/deleted copy/move constructors and operators.

Again, this paper has a very good abstract:

Some classes only work in certain contexts: scope guards are usually useless as subexpressions, and expression template placeholders malfunction as local variables. An unused function result may signal that the caller intends to follow a different protocol, such as with std::async. This proposal extends class definitions to prevent such errors, and adds a mechanism to automatically resolve them by type substitution, such as a value type for an expression template. Additionally, generation of non-movable objects becomes more tractable. The added functionality includes that of the “auto evaluation” proposals. This proposal intends to be more expressive, more broadly applicable, and easier to adopt and use.

This proposal wants to add the restrict qualifier to C++, as it already exists in C99, and some compilers already provide restrict as an extension so C++. As restrict is currently not defined, the authors seek to define how to correctly use it, especially with C++11 features such as lambdas.

This paper wants to improve the usability of std::uncaught_exception:

The function int std::uncaught_exceptions() returns the number of exception objects that have been initialized and thrown or rethrown but for which no handler has been activated

This proposal wants to make assert a language construct, and not a macro:

The assert macro has never behaved much like a real function, and for the foreseeable future, it will look and smell like an operator. The way the macro is specified by C precludes it from providing optimization hints in production mode, but allows it to execute arbitrary side effects in debug mode. Adding assert as a keyword and built-in operator would have benefits but essentially no downside.

This paper analyzes to how to support contract programming-like features in C++. It tries to give an overview, on how contracts could be supported during compilation time rather then run time checking. The authors define the scope of the document as:

While [N1962] is a fairly complete proposal for adding contract-programming support to C++, this document offers an analysis of the problem domain rather than making a specific proposal. We focus on identifying expectations, potential implementation difficulties and costs. All other contract programming proposals that we are aware of — [N4075], [N4110] — start from the assumption that the support for preconditions must be provided in the from of evaluating preconditions before function calls, occasionally disabling these precondition evaluations, and installing broken contract handlers. In this paper, we do not take this assumption for granted. Run-time support is only a subset of the scope of our analysis. We explore in more detail an alternative approach: the focus on static analysis

Formally known as universal references, the type T&& is always a r-value reference, except its a template argument or used with auto. Technically still being an r-value reference, but behaving in this environment very different. For now the standard does not recognize this, the authors want to introduce the term forwarding reference for r-value references in templates and auto.

The call syntax for members is x.f() or x->f(), while it is for nonmembers f(x), this is a problem in generic code, as there is no unified call syntax, generic code must decide which it wants to call members or non-members.

To resolve this, the authors propose to also allow the call syntax of x./->f() for free functions, if the first argument of the free function is a pointer or reference to x. This would also nicely go along with C, where often the first pointer argument is a struct to which the function belongs. The authors use FILE* and fseek as an example.

Currently, std::initializer_list isn't moveable, as it was originally designed before move-semantics became important. Also, back then it seemed sufficient to only provide copy semantics, but this has changed today. The author proposes a templated version of std::intializer_list which is derived from its non r-value intializer_list type:

template< typename T > struct initializer_list< T && > : initializer_list< T > {

This construct then also implements ownership and move-semantics.

This looks similar what is proposed for ranged-for, but has a different meaning in a similar (almost same) syntax. The authors propose that within a function call, you can assign values to named parameters such as:

void foo(int x, int y, int z);

foo(40,z: 30, y:20);

So, here in the call, z gets the value of 30 assigned, the 40 then will end up in x. Non named arguments cannot follow after a named argument.

Again, a very good abstract from the paper:

This is a proposal to allow user - defined operator dot ( operator.() ) so that we can provide “smart references” similar to the way we provide “smart pointers.” The gist of the proposal is that if an operator.() is defined for a class Ref then by default every operation on a Ref object is forwarded to the result of operator.(). How ever, an operation explicitly declared as a member of Ref is applied to the Ref object without forwarding. A programmer can prevent a pointer to a referred - to object from “leaking” into the larger program by declaring operator.() private.

This is like N4165 - unified call syntax. Short description:

The basic suggestion is to define x.f(y) and f(x,y) to be equivalent. In addition , to increase compatibility and modularity, I suggest we explore the possibility of ignoring uncallable and inaccessible members when looking for a member function (or function object) to call.

This paper discusses very broadly how default comparisons should look like in C++, and what it would mean to be able to declare comparison operators as default. This is closely related to N4126 and the reflection paper N4239.

Bjarne sums up the discussion on default comparison:

This is a summary of some of the discussion of my Default Comparisons draft. It lists the various proposals and compares them according to a set of criteria. I would be particularly interested in comments about the choice of comparison criteria (pun intended).

Join the Meeting C++ patreon community!

This and other posts on Meeting C++ are enabled by my supporters on patreon!