Header lexy/callback/container.hpp

Callbacks and sinks for constructing containers.

Callback and sink lexy::as_list and lexy::as_collection

lexy/callback/container.hpp
namespace lexy
{
    template <typename Container>
    struct as-container
    {
        //=== callback ===//
        using return_type = Container;

        constexpr Container operator()(lexy::nullopt) const;
        constexpr Container operator()(Container&& container) const;

        template <typename ... Args>
        constexpr Container operator()(Args&&... args) const;
        template <typename ... Args>
        constexpr Container operator()(const Container::allocator_type& allocator,
                                       Args&&... args) const;

        //=== sink ===//
        struct sink-callback
        {
            using return_type = Container;

            template <typename T>
            constexpr void operator()(T&& obj);

            template <typename ... Args>
            constexpr void operator()(Args&&... args);

            constexpr Container finish() &&;
        };

        constexpr sink-callback sink() const;
        constexpr sink-callback sink(const Container::allocator_type& allocator) const;

        //=== allocator ===//
        template <typename AllocatorFn>
        constexpr auto allocator(AllocatorFn allocator_fn) const;

        constexpr auto allocator() const
        {
            return allocator(identity-fn);
        }
    };

    template <typename Container>
    constexpr as-container<Container> as_list;

    template <typename Collection>
    constexpr as-container<Container> as_collection;
}

Callbacks and sink to construct the given Container.

as_list is used for positional containers like std::vector. It calls .push_back() and .emplace_back(). as_collection is used for non-positional containers like std::set. It calls .insert() and .emplace().

As a callback, they have the following overloads:

(lexy::nullopt)

Returns an empty container by default constructing it.

(Container&& container)

Forwards an existing container unchanged.

(Args&&…​ args)

Repeatedly calls .push_back()/.insert() on an empty container; if called with N arguments, the resulting container will contain N items. If .reserve(sizeof…​(args)) is well-formed, calls that first. The final container is returned.

(const typename Container::allocator_type& allocator, Args&&…​ args)

Same as above, but constructs the empty container using the given allocator.

As a sink, .sink() can be called with zero arguments or with one argument of type Container::allocator_type. In the first case, it default constructs an empty container. In the second case, it constructs it using the allocator. The resulting sink callback has the following overloads and returns the finished container:

(T&& object)

Calls .push_back()/.insert() on the container.

(Args&&…​ args)

Calls .emplace_back()/.emplace() on the container.

The .allocator() function takes a function that obtains the allocator from the parse state. If the function is not provided, it uses the parse state itself as the allocator. It returns a new callback and sink that accepts the parse state. It then has the same overloads and behaviors, except that it will always use the allocator obtained via the allocator_fn from the parse state. As a callback, this behavior is similar to lexy::bind  where the allocator is bound via lexy::parse_state . As a sink, this behavior is similar to lexy::bind_sink  where the allocator is bound via lexy::parse_state .

Example 1. Construct a list of integers
struct production
{
    static constexpr auto whitespace = dsl::ascii::space;

    static constexpr auto rule = [] {
        auto integer = dsl::integer<int>;
        return dsl::list(integer, dsl::sep(dsl::comma));
    }();

    static constexpr auto value = lexy::as_list<std::vector<int>>;
};
Example 2. Construct a list of integers with a custom allocator
struct state
{
    std::allocator<int> allocator; // The allocator that should be used.
    // Potentially other members here.
};

struct production
{
    static constexpr auto whitespace = dsl::ascii::space;

    static constexpr auto rule = [] {
        auto integer = dsl::integer<int>;
        return dsl::list(integer, dsl::sep(dsl::comma));
    }();

    static constexpr auto value
        // Pass the allocator to the sink.
        = lexy::as_list<std::vector<int>>.allocator(&state::allocator);
};

Callback and sink lexy::concat

lexy/callback/container.hpp
namespace lexy
{
    template <typename Container>
    struct concat-impl
    {
        using return_type = Container;

        constexpr Container operator()() const;
        constexpr Container operator()(lexy::nullopt) const;
        constexpr Container operator()(Container&& head, Container&&... tail) const;

        constexpr sink auto sink() const;
    };

    template <typename Container>
    constexpr concat-impl concat;
}

Callback and sink that concatenates multiple containers.

As a callback, it accepts zero or more existing containers, and will concatenate them together. This is done by repeatedly calling .append() or .push_back() on the first container.

As a sink, it creates a default constructed container object as the current result. It can then be invoked with a single container object. If the current result is still empty, the new container is move assigned into it. Otherwise, the new container is appended by calling .append() or .push_back().

Example 3. Construct a list of integers from a list of list of integers
struct list
{
    static constexpr auto rule = [] {
        auto integer = dsl::integer<int>;
        return dsl::list(integer, dsl::sep(dsl::comma));
    }();

    static constexpr auto value = lexy::as_list<std::vector<int>>;
};

struct production
{
    static constexpr auto whitespace = dsl::ascii::space;
    static constexpr auto rule       = dsl::list(dsl::p<list>, dsl::sep(dsl::newline));
    static constexpr auto value      = lexy::concat<std::vector<int>>;
};

Sink lexy::collect

lexy/callback/container.hpp
namespace lexy
{
    constexpr sink<> auto collect(callback auto&& callback)
        requires std::is_void_v<callback-return-type>;

    template <typename Container>
    constexpr sink<> auto collect(callback auto&& callback);
        requires !std::is_void_v<callback-return-type>;
}

Turns a callback into a sink by collecting all results.

The first overload requires that the callback returns void. It returns a sink that repeatedly invokes callback and produces the number of invocations as a std::size_t.

The second overload requires that the callback does not return void. .sink() can be called with zero arguments or with one argument of type Container::allocator_type. In the first case, it default constructs an empty container; in the second case, it constructs it using the allocator. The sink callback just forwards to callback and adds the result to the container by calling .push_back(). The final container is returned.

Note
See lexy::callback  for the inverse operation that turns a sink into a callback.
Tip
Use collect for the error callback. It will collect all errors into a single container.

See also