Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Concept that requires a member function with an OutputIterator as parameter

Tags:

I am playing with concepts and hit a roadblock. Or maybe it is just my mind that is blocked.

I want to create a class that buffers a "bulk-readable" data source. Such a datasource should have a member function that accepts an OutputIterator and has a signature like:

template<typename It>
size_t read(It firstItem, size_t max)

My idea was to define a BulkReadable concept similar to:

template<typename Source>
concept bool BulkReadable = 
    requires(Source s, Iter out, size_t max) {
        {s.read(out, max)} -> size_t;
    };

I am having trouble specifying Iter. I could add another typename to the template parameter list, but then the Buffer class that wants to use the concept would need to specify the type of that parameter.

The ideal way how I would like to use the concept is:

template<BulkReadable Source>
class Buffer {
  public: 
    Source& input:
    Buffer(Source& input) : input(input){}
    ...     

Is this approach even viable? If yes, how can I require a templated method signature if I do not want/can specify the type?

like image 343
Christian Ott Avatar asked Apr 26 '20 15:04

Christian Ott


1 Answers

This is a common problem of asking the wrong question of a concept, where you're trying to use them like you would a base class interface. With base classes, you're declaring the exact, specific functions that the derived classes are to implement. You want the user to implement exactly the function you say they must.

With concepts, you approach the issue from the other direction: what usage are you trying to create?

At some point in your code, you have some object, some iterator, and a size. And you're going to take that object, call a function by passing it the iterator and the size, and you expect a response back of a certain type. And this process has some meaning.

Then that is your concept. It's a concept that is based on at least 2 parameters: the type of the object and the iterator type. So that's what you should create.

If you have this BulkReadable constraint, then you must have some interface constrained on it. An interface that's going to call read. To call read, that interface must have an iterator.

So here are the options:

  1. The interface is given an iterator type (directly or indirectly) by the user. If that's the case, then you just use that type in the function's BulkReadable constraint. If the iterator type is based on a complex set of operations on parameters, then you'll have to do some computations to compute the iterator type.

  2. The iterator is statically determined. Then just use the known iterator type in the constraint.

The point being that, at the point where you're going to try to call read, you know what the iterator type is. And therefore, you can constrain things using that type. Your concept therefore is not really BulkReadable, but BulkReadableFrom.

In short, you shouldn't want to constrain a type on being able to take any type (or any type within some constraints). Check constraints against the actual types you're going to use them with, preferably at the point where they become relevant.

like image 190
Nicol Bolas Avatar answered Oct 02 '22 16:10

Nicol Bolas