C++ Logo

std-proposals

Advanced search

Re: Make specifying all necessary concepts mandatory

From: Barry Revzin <barry.revzin_at_[hidden]>
Date: Tue, 7 Jan 2020 17:20:09 -0600
On Mon, Jan 6, 2020 at 8:33 PM Arthur O'Dwyer via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> 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>;
>

It's important to point out that C++0x Concepts weren't *just* check the
definition to see if it's viable. They *also* ensure that it actually
works. That is, if you had a concept like:

template <typename T>
concept Addable {
    typename result;
    result operator+(T const&, T const&);
};

template<Addable T>
    requires Constructible<decay_t<T::result>, T::result>
auto add(T x, T y) { return x + y; }

Then:

1) Note that I added the Constructible requirement - because the template
actually needs it and wouldn't compile otherwise.
2) add<Evil1> would be perfectly fine - the x + y operation there doesn't
redo overload resolution. The + itself goes through a concept map and would
invoke int operator+(Evil const&) const, because that's what satisfied the
concept. The int operator+(Evil&) = delete overload doesn't play here.
3) add<Evil2> would be fail because we had to add the requirement in (1) to
begin with, and we would've had to add that requirement in order to get the
definition of the template to compile.

Barry

Received on 2020-01-07 17:22:47