Home > OS >  How to properly constrain an iterator based function using concepts
How to properly constrain an iterator based function using concepts

Time:02-17

I have trouble understanding the concepts defined in the <iterator> header. I couldn't find good examples using them.

I tried to use the concepts in a simple function to provide better error messages. I don't understand why the output_iterator takes a type but input_iterator doesn't any. Furthermore I don't know how to make the random_assign_working function more generic.

template<typename T, std::input_iterator InputIter, std::output_iterator<T> OutputIter>
requires std::random_access_iterator<InputIter>
auto random_assign(InputIter inputBegin, InputIter inputEnd, OutputIter outBegin) {
  auto inputSize = std::distance(inputBegin, inputEnd);
  for (size_t it = 0; it < inputSize; it  ) {
    auto randomIndex = rand()%inputSize;
    auto selectedInput = inputBegin[randomIndex]; // read input
    *outBegin = selectedInput; // write output
    outBegin  ;
  }
}

template<std::input_iterator InputIter, std::output_iterator<uint32_t> OutputIter>
requires std::random_access_iterator<InputIter>
auto random_assign_working(InputIter inputBegin, InputIter inputEnd, OutputIter outBegin) {
  auto inputSize = std::distance(inputBegin, inputEnd);
  for (size_t it = 0; it < inputSize; it  ) {
    auto randomIndex = rand()%inputSize;
    auto selectedInput = inputBegin[randomIndex]; // read input
    *outBegin = selectedInput; // write output
    outBegin  ;
  }
}

int main() {
  {
    std::array<uint32_t, 9> input = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    std::forward_list<uint32_t> output;
    random_assign(input.begin(), input.end(), std::front_insert_iterator(output)); // how can I make this work
    random_assign_working(input.begin(), input.end(), std::front_insert_iterator(output)); // working

    std::stringstream buf {};
    buf << "Out ";
    for (auto item : output)
      buf << " " << item;
    std::cout << buf.str() << std::endl;
  }
  {
    std::array<std::string, 9> input = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
    std::vector<std::string> output;
    output.reserve(input.size());
    random_assign(input.begin(), input.end(), output.begin()); // how can I make this work
    random_assign_working(input.begin(), input.end(), std::front_insert_iterator(output)); // how can I make this work
  }
}

So my questions are: How to make use of the concepts defined in <iterator> when writing functions. How do the iterator_tags tie into this? How do I check for iterator_tags?

I would appreciate learning resources and pointing me in the right direction.

CodePudding user response:

I don't understand why the output_iterator takes a type but input_iterator doesn't any.

Because input iterators have concrete type: it's the type you get from *it. That's the iterator's reference type (iter_reference_t<I>).

But output iterators don't - they just have a set of types that you can write into them. It doesn't matter what *out gives you, it matters what types you can put into *out = x; You can't ask for something broad like... give me all possible types you can write into my prospective output iterator type, O. But you can ask O if it accepts a specific type T.

For instance, int* is an input iterator whose reference type is int&. But int* can also be used a an output iterator - but for a wide variety of types. int of course, but also int16_t, or uint64_t, or char, or any other type convertible to int.

Hence, you can ask if I is an input_iterator, but you can only ask if O is an output_iterator for some type you're writing into it T.


Let's go through your random_assign:

template<typename T, std::input_iterator InputIter, std::output_iterator<T> OutputIter>
    requires std::random_access_iterator<InputIter>
auto random_assign(InputIter inputBegin, InputIter inputEnd, OutputIter outBegin);

First up, random_access_iterator refines input_iterator, so you don't need to write both. Just pick the most specific constraint. Once you do that, the type you're writing into outBegin is your input iterator's refernece type, so that's the constraint you need:

template <std::random_access_iterator I, std::output_iterator<std::iter_reference_t<I>> O>
void random_assign(I first, I last, O out);

Next, you want to return useful information. In this case, we're advancing out a bunch of times, so we should return its new value.

Full thing:

template <std::random_access_iterator I, std::output_iterator<std::iter_reference_t<I>> O>
auto random_assign(I first, I last, O out) -> O {
    // you can use distance, but we know it's random access
    size_t const dist = last - first;

    for (size_t i = 0; i != dist;   i) {
        // output_iterator requires this work
        *out   = first[rand() % dist];
    }

    // law of useful return
    return out;
}
  • Related