On Fri, 29 Apr 2022 at 19:47, Marios Staikopoulos <marios@staik.net> 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.