Document no. P0915R0
Date 2018-02-08
Reply-to Vittorio Romeo <vittorio.romeo@outlook.com>, John Lakos <jlakos@bloomberg.net>
Audience Evolution Working Group
Project ISO JTC1/SC22/WG21: Programming Language C++

Concept-constrained auto

Abstract

This paper proposes the addition of a concept-constrained auto placeholder type for variables. The primary goal is to increase code readability and correctness without sacrificing genericity:

template <typename T>
void foo(std::vector<T>& v)
{
    auto<RandomAccessIterator> it{std::begin(v)};
    // ...
}

Table of contents

Overview

A large part of the Concepts TS 1 has been merged into the C++20 Standard working draft 2. Due to the lack of consensus and also to the existence of reasonable concerns, proposed features such as placeholders (changes to [dcl.spec.auto]) and abbreviated templates (changes to [dcl.fct] and [temp]) have not yet been merged into the working draft.

This paper proposes the introduction of a concept-constrained auto type placeholder, focusing on only the simplest, most common, and most impactful use case, while leaving the door open for future extensions.

The proposed concept-constrained auto enables users to specify a (constraining) concept-name when declaring variables using auto:

std::list<int> aList;

// Unconstrained `auto`:
auto i0 = std::begin(aList);

// Well-formed usage of concept-constrained `auto`:
auto<BidirectionalIterator> i1 = std::begin(aList);

// Ill-formed usage of concept-constrained `auto`:
auto<RandomAccessIterator> i2 = std::begin(aList); // <== compile-time error

Motivation

The current unconstrained auto placeholder works well in many scenarios:

Unfortunately, the inability to constrain auto with a concept often results in less-readable code, sometimes with surprising results. Consider the following cases:

We are confident that proper use of concept-constrained auto, where appropriate, will significantly increase the readability and accessibility of Modern C++ programs, especially in large code bases. Note that we are not suggesting that concept-constrained auto should always be preferred to (unconstrained) auto -- there are common situations where supplying the optional constraint would be counter indicated:

Employee* Company::findFirstSeniorEmployee() const
{
    auto<Iterator> it = std::begin(this->d_employees);
//      ^~~~~~~~~~

    // ...business logic...
    return it == std::end(this->d_employees) ? nullptr
                                             : &*it;
}

In the example above, the identifier it is already clearly some form of Iterator. Moreover, the Iterator concept, along with its idiomatic use as being what is expected of the return types for begin() and end() methods, are familiar to virtually every C++ developer. (We refer to such ubiquitously familiar concepts as vocabulary concepts). Making Iterator an explicit (named) constraint to auto here would be pointless noise – arguably detracting from readability. If, on the other hand, it were the case that (1) the code did not as yet make explicit use of random-access-iterator features, and (2) it was foreseeable that such features would soon be required, then constraining the auto with RandomAccessIterator is exactly what would express that engineering intent:

Employee* Company::findFirstSeniorEmployee() const
{
    auto<RandomAccessIterator> it = std::begin(this->d_employees);
//      ^~~~~~~~~~~~~~~~~~~~~
//      Required to express engineering intent.

    // ...business logic that does not require a `RandomAccessIterator` (yet)...
    return it == std::end(this->d_employees) ? nullptr
                                             : &*it;
}

From an engineering standpoint, however, the use of concept-constrained auto becomes imperative, when dealing with non-vocabulary concepts, as this compiler-enforced “documentation” dramatically facilitates developers becoming familiar with previously unseen concepts by name, which – in turn – can be more easily looked up if need be:

void Employee::logStatus() const
{
    auto<bdex::OutStream>& stream = getLogStream();
//      ^~~~~~~~~~~~~~~~~

    stream.putString(this->d_name);
    stream.putUint32(this->d_userId);
    stream.putUint8(this->d_statusFlags);
}

Developers reading the function above will benefit greatly from the explicit bdex::OutStream concept constraint, especially those who find it unfamiliar:

If (unconstained) auto were used, the reader would need to check the declaration of getLogStream() (and possibly its definition) in order to understand what kind of object was being returned.

Proposed syntax

We propose the ability of constraining auto with a concept, independently of cv-qualifiers. E.g.

auto<Iterator> i0 = foo();
auto<BidirectionalIterator>&& i1 = foo();
const volatile auto<MoveConstructible>& i2 = bar();

Wording

The proposed wording copies the terminology used in N4674 in order to make future extensions easier to apply.

[dcl.type.simple]

[dcl.spec.auto]

[dcl.spec.auto.deduct]

[dcl.spec.auto.constr]

Future directions

This proposal is meant to be as simple as possible but leaves room for future extensions.

The goal of this proposal is to introduce a simplified yet useful minimal subset of the proposed placeholder sections in the Concepts working draft. Concept-constrained auto’s scope and functionality can then be expanded in the future to reach the power and flexibility of the original design.

Bikeshedding

<...> is, in our view, the optimal choice due to its clear, concise, and expressive syntax. Unfortunately, it might not be appropriate as it misleadingly suggests template instantiation. Andrew Sutton recommended using the auto|Concept syntax instead, which was part of the original proposal for concepts but was dropped in favor of the terse notation which we have already addressed. There exist other viable possibilities for the concept-constrained auto syntax:

We have not considered auto(Concept) and auto[Concept] because they respectively conflict with variable initialization and structured bindings.

References


  1. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4674.pdf

  2. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4700.pdf