C++ Logo

std-discussion

Advanced search

Re: Question regarding how to declare templated functions inside a concept declaration

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Fri, 29 Apr 2022 20:48:52 +0100
On Fri, 29 Apr 2022 at 19:47, Marios Staikopoulos <marios_at_[hidden]> wrote:

> I’m not sure if there is a misunderstanding, but the IterationAlgorithm
> concept, and other dependent concepts, need only to check that the function
> signatures are conforming to the requirements, not that they compile
> correctly – that error checking regarding movability and other constraints
> will happen during compilation normally, ie:
>
> struct IterationAlgorithm_Impl
> {
>
> template<Iterator T>
> void apply(T begin, T end) {
> // …. Do stuff here
> }
>
> };
>
>
>
> // Verify that our implementation satisfies the concept
>
> static_assert(InterationAlgorithm<IterationAlgorithm_Impl>);
>
> > Even for constraints expressed entirely syntactically, there's a wider
> problem which is that a compiler given the task of constructing archetypes
> is *required* to do so in the laziest, most infuriatingly pettifogging way
> possible, You specified that { cbuf.HasData() } -> std::same_as<bool>; for
> cbuf T const, which is fine, but that doesn't say anything about being able
> to call HasData on a T lvalue. Or a T or T const prvalue or xvalue. Or if
> your Iterator is random-access and thus addable with int, then it's obvious
> to humans that it should also be addable with unsigned, long etc., but not
> to a compiler.
>
>
> In concept IterationAlgorithm, we don’t need to know that the code
> compiles correctly or satisfies the constraints at the declaration of the
> concept.. that isn’t its responsibility – its responsible only for
> verifying that the proper signatures required exist. In this case, all the
> IterationAlgorithm wants to check for is that there is a member function
> apply that takes in any Iterator concept as arguments.
>

Right, and that IterationAlgorithm_Impl does not satisfy
IterationAlgorithm, since it *does not have such a member function*.
Instead it takes arguments that satisfy Iterator *and are movable*.

Whether or not the apply() uses the constraints, and semantics of Iterator
> correctly will happen when IterationAlgorithm_Impl is compiled.
>

I'm not talking about the internals of apply(); those are irrelevant.

Suppose your Iterator concept is:

template<class T> concept Iterator = requires(T t) {
    { ++t } -> std::same_as<T&>;
};

Then:

struct EvilIterator {
    EvilIterator& operator++() &;
    ~EvilIterator() = delete;
};
static_assert(Iterator<EvilIterator>);

But:

struct IterationAlgorithm_Impl {
    template<Iterator T>
    void apply(T begin, T end) {
        // …. Do stuff here
    }
};
template<class A>
concept IterationAlgorithm = requires(A a, EvilIterator it) {
    { a.apply(it, it) } -> std::same_as<void>;
};
static_assert(IterationAlgorithm<IterationAlgorithm_Impl>);

The latter static_assert fails because the concept IterationAlgorithm is
*implicitly* requiring that the Iterator type is copyable.

And in practice, much of the time you will have additional implicit
constraints in the form of (deduced or) calculated return types, along with
potentially specialized class and variable templates.

Received on 2022-04-29 19:49:04