Home > Mobile >  How can you require that a concept parameter is a reference `&` type?
How can you require that a concept parameter is a reference `&` type?

Time:06-28

In the following concept implementation struct, String, the operator() method does not take the value Source by reference but still satisfies the concept Consumer. How can I prevent String from being a valid Consumer, specifically by requiring Source source to be a reference &?

template <class Type, class Source>
concept Consumer = requires(Type type, Source & source, std::ranges::iterator_t<Source> position) {
    { type.operator()(source, position) } -> std::same_as<Source>;
};

struct String {
    public:
        std::string operator()(const std::string source, std::string::const_iterator position) {
            return "test";
        }
};

int main(const int, const char * []) {
    String consumer{};
    static_assert(Consumer<String, std::string>);
    auto test = std::string("test");
    consumer(test, std::begin(test));
}

CodePudding user response:

You cannot (reasonably) detect if takes a reference parameter. But you can detect if it can take an rvalue:

template <class Type, class Source>
concept ConsumerOfRvalue = requires(Type type, Source &&source, std::ranges::iterator_t<Source> position) {
    type(std::move(source), position);
};

If the function takes its parameter by &&, by const&, or by value (assuming Source is moveable), then it will satisfy ConsumerOfRvalue. Therefore, you can make your Consumer require that the types not satisfy ConsumerOfRvalue:

template <class Type, class Source>
concept Consumer = !ConsumerOfRvalue<Type, Source> &&
    requires(Type type, Source & source, std::ranges::iterator_t<Source> position) {
    { type(source, position) } -> std::same_as<Source>;
};

CodePudding user response:

First of all, there is no reason to write type.operator()(source, position). That's weird, and it simply excludes some kinds of callables for no benefit.

There's also no difference between having a "parameter" in a requires-expression be a reference and a value type, unless you're going to use decltype(the_parameter) in the body somewhere, which you're probably not going to do, so can drop the &.

Lastly, type is not a particularly informative name for a callable. So let's start with:

template <class F, class Source>
concept Consumer = requires (F f, Source source, std::ranges::iterator_t<Source> it) {
    { f(source, it) } -> std::same_as<Source>;
};

Alright, now to your actual question:

How can I prevent String from being a valid Consumer, specifically by requiring Source source to be a reference &?

You can't. Concepts don't check signatures, they check expression validity, and it's a valid expression to copy the range (assuming it's copyable of course) even if that's not what you want to happen. Please don't start checking the type of operator() (that prevents using an overloaded function object, templates, or any default arguments). So one option is to just deal with it.

Another is to pass something into f for which copying isn't an issue. Like... &source. That makes your Consumer implementations more awkward, because... pointers.

Another is to change the API to something that is less likely to be incorrectly implemented. Like instead of passing in (source, it), pass in subrange(it, ranges::end(source)). If the Consumer specifically needs the original range, this is problematic. If they just need a range, this works fine. Alternatively, instead of an iterator, pass an index that isn't tied to the original source. If the Consumer only operates on random access ranges, this makes the copy less of a problem (just an expense), but if the Consumer needs to operate on weaker ranges, this could be an unacceptable expense.

  • Related