Tomáš Nadrchal
Tomáš Nadrchal

Reputation: 147

Variadic templates and constraints

I have a variadic template function that takes as parameters file name, delimiter, and non-specified number of containers as columns. This function then parses to file all values in containers that represent columns and between all values put delimiter.

bool parseToFile (const std::string& file, const char delimiter, auto&... columns)
{
    bool done {false};
    std::ofstream out;
    out.open (file, std::ios::out | std::ios::trunc);
    if (!out)
    {
        done = false;
        std::cout << "SaveFile: Cannot open file: " << file << "!" ;
        std::cout << std::strerror (errno) << std::endl;
    }
    else
    {
        std::size_t maxContainerSize {0};
        auto maxSize = [&maxContainerSize] (std::size_t containerSize)
        {
            maxContainerSize = std::max (maxContainerSize, containerSize);
        };
        (maxSize (columns.size()), ...);
        std::size_t i {0};
        auto implementation = [&i, delimiter, &out] (auto & cont, std::size_t s)
        {
            if (i < cont.size())
                out << cont[i];

            out << delimiter;
        };
        for (; i < maxContainerSize; ++i)
        {
            (implementation (columns, columns.size()), ...);
            out << "\n";
        }
        out << std::flush;
        done = true;
    return done;
}

This works as intended correctly.

But this template has several constraints releating to what can be parameter pack "columns"

How should I change my code to implement this constraint?

I have tried this:

template <template <typename, typename> class Container,
          typename Value,
          typename Allocator = std::allocator<Value>>
concept containerHasPrintable = requires (std::ostream& out, Container<Value,Allocator>& data)
{
    out << data[0];
};

// and definition:
bool parseToFile (const std::string& file, const char delimiter, containerHasPrintable&... columns);

But this does not work. GCC 13.1

main.cpp|80|error: wrong number of template arguments (1, should be at least 2)| "80 - function"
main.cpp|40|note: provided for 'template<template<class, class> class Container, class Value, class Allocator> concept containerHasPrintable'| "40 - concept"
main.cpp|80|error: expansion pattern 'int&' contains no parameter packs|

How can I constrain the template?

Upvotes: 3

Views: 131

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275730

I'd break it down into a few sub-concepts.

This has a few advantages. First, the subsuming rules like it when you do this. Second, you get better error messages -- being told which concepts fail by name. Third, it is somewhat better in self documentation.

template<class Container>
concept IndexedContainer = requires(Container const& data) {
  data[0];
  std::size(data);
};
template<class T>
concept CanStreamOut = requires(T t, std::ostream& os) {
  os << t;
};
template<class C>
concept ElementCanStreamOut = requires(C const& c) {
  { c[0] } -> CanStreamOut;
};
template<class C>
concept PrintableRange = IndexedContainer<C> && ElementCanStreamOut<C>;

The const& portion helps block std::map from passing these tests.

Upvotes: 1

Barry
Barry

Reputation: 303337

This:

template <template <typename, typename> class Container,
          typename Value,
          typename Allocator = std::allocator<Value>>
concept containerHasPrintable = /* ... */;

Is a concept that is constraining three things: a template and two types. But that's not actually what you want - you want a concept that is constraining one thing: a type.

So what you want to write is just that:

template <typename Container>
concept PrintableContainer = requires (std::ostream& out, Container& data)
{
    out << data[0];
};

Note that none of the other stuff in your concept is actually relevant - you don't need the container to specifically look like C<V, A> anyway - and this just arbitrarily rejects some other types that would otherwise work (like, say, std::string).

Upvotes: 5

Related Questions