Search code examples
c++constraintsc++20variadic-templates

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"

  • it should be a container
  • any container should overload operator [] to get access to objects (eg. std::vector, std::deque)
  • any object in the container should have operator << to be able to work with iostream

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?


Solution

  • 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).