C++ Logo

std-proposals

Advanced search

Re: Make specifying all necessary concepts mandatory

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Mon, 6 Jan 2020 21:32:53 -0500
On Fri, Jan 3, 2020 at 3:46 PM Askar Safin via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> TL;DR: Require users to specify all necessary concepts in template. This
> is, of course, incompatible change, so it should be implemented using
> epochs ( http://wg21.link/P1881 ). This is what templates should be right
> from the beginning.
>

You seem to be talking about something basically the same as what used to
be called "definition checking." The idea would be that if we had some way
to specify that a type was "addable," then we could write

    template<Addable T> auto add(T x, T y) { return x + y; }

The problem with this in C++2a is that C++2a Concepts are "Concepts Lite";
they do not, and fundamentally cannot, support definition checking.
Consider what would happen if you wrote the following C++2a code:

    template<class T>
    concept Addable = requires(const T& t) {
        { t + t };
    };

Without definition checking, our "add" function template above compiles
fine (because you don't have to do any checking on the template itself),
and you can even instantiate it with common-law-addable types, so that we
can form `add<int>` and `add<std::string>`.
But if we blindly added definition checking to C++2a Concepts Lite, then
our code would no longer compile! Definition checking means that we don't
check just when we instantiate the template; the compiler would actually *check
the definition of the template itself* and inform us that the template uses
functionality that is not reflected in the Addable concept.

Proof by example:
    // https://concepts.godbolt.org/z/BL5Vam
    struct Evil1 {
        int operator+(const Evil1&) const;
        int operator+(Evil1&) = delete;
    };
    static_assert(Addable<Evil1>); auto failure1 = &add<Evil1>;

Proof by a different example:
    struct Evil2 {
        Evil2& operator+(const Evil2&) const;
        Evil2(Evil2&&) = default;
    };
    static_assert(Addable<Evil2>); auto failure2 = &add<Evil2>;

See, `concept Addable` is checking for the existence of `t + t` on const
lvalues, but our `add` template actually tries to use it on non-const
lvalues. So definition-checking would complain about our `add` template
because it knows `Evil1` *might* exist.
And `concept Addable` doesn't constrain the return type of `x + y`. Our
`add` template tries to return that expression by value, which is totally
fine if `operator+` returns a prvalue; but definition-checking complains
about our `add` template because it knows `Evil2` *might *exist.

The natural response would be "Okay, but we can fix that by adding epicycles
<https://en.wikipedia.org/wiki/Deferent_and_epicycle> on top of Concepts
Lite." You might say that `concept Addable` should be written as

    template<class T>
    concept PrvalueOrCopyable = std::object<T> ||
std::copy_constructible<std::remove_cvref_t<T>>;

    template<class T>
    concept Addable = requires(const T cv, T v, const T&& cr, T&& r) {
        { cv + cv } -> PrvalueOrCopyable;
        { cv + v } -> PrvalueOrCopyable;
        { cv + cr } -> PrvalueOrCopyable;
        // etc etc etc
    };

However, even that is not good enough for our `add` template. (Proof left
as an exercise for the reader.) And it's certainly much *much* more
boilerplate than we want programmers to have to write.

Concepts Lite, like most high-level programming language features, is a
feature for *human programmers*, not a feature for computers. If you just
think of it as a clever way of writing in-line documentation, you won't be
quite so tempted to try to express everything in the world in terms of it.
There are many things that C++2a Concepts can't do. Definition checking is
one of them.

There are ways that Concepts could have been done that would have permitted
definition checking, but that ship sailed when Concepts Lite was
standardized. We physically cannot get definition checking now, for the
reasons explained above.

See also:
https://lists.isocpp.org/sg20/2019/12/0089.php (SG20 mailing list, last
month)
https://quuxplusone.github.io/blog/2018/06/12/attribute-noexcept-verify/
(on the difficulty of expressing constraints syntactically)
https://quuxplusone.github.io/blog/2019/09/18/cppcon-whiteboard-puzzle/
(ditto)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0782r2.html (on
how ADL will screw it all up anyway — one solution to the exercise above)

HTH,
–Arthur

Received on 2020-01-06 20:35:36