January 30th, 2025

Creating a generic insertion iterator, part 1

Last time, we created an inserter iterator that does unhinted insertion. We noticed that most of the iterator is just boilerplate, so let’s generalize it into a version that is all-boilerplate.

// Do not use: See discussion
template<typename Lambda>
struct generic_output_iterator
{
    using iterator_category = std::output_iterator_tag;
    using value_type = void;
    using pointer = void;
    using reference = void;
    using difference_type = void;

    generic_output_iterator(Lambda&& lambda) :
        insert(std::forward<Lambda>(lambda)) {}

    generic_output_iterator& operator*() noexcept
        { return *this; }
    generic_output_iterator& operator++() noexcept
        { return *this; }
    generic_output_iterator& operator++(int) noexcept
        { return *this; }

    template<typename Value>
    generic_output_iterator& operator=(
        Value&& value)
    {
        insert(std::forward<Value>(value));
        return *this;
    }

protected:
    std::decay_t<Lambda> insert;

};

template<typename Lambda>
generic_output_iterator<Lambda>
generic_output_inserter(Lambda&& lambda) {
    return generic_output_inserter<Lambda>(
        std::forward<Lambda>(lambda));
}

template<typename Lambda>
generic_output_iterator(Lambda&&) ->
    generic_output_iterator<Lambda>;

For convenience, I provided both a deduction guide and a maker function, so you can use whichever version appeals to you.¹

The generic output iterator uses the lambda to process each insertion. You can make the lambda do anything you like. For example:

auto sample(std::vector<int>& v)
{
    std::map<int> m;
    std::copy(v.begin(), v.end(),
        generic_output_iterator(
            [&m, hint = m.begin()](int v) mutable {
            hint = m.insert(hint, { v, 0 });
        }));
}

In this example, we take a vector of what we expect to be a mostly-sorted sequence of integers and use them as keys in a newly-created map, where the associated integer is initialized to zero. We take advantage of the mostly-sorted-ness by using the location of the previously-inserted item as the hint for the next item.

Too bad this is not a valid iterator!

Among the requirements for iterators is that they be default-constructible and assignable, and ours is neither because capturing lambdas are neither default-constructible nor assignable. (Capturing lambdas can be copy-constructible and move-constructible, but they are never default-constructible or assignable.)

We’ll try to fix this problem next time.

¹ The C++ standard library has a lot of maker functions because they predate class template argument deduction (CTAD) and deduction guides.

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments