c++c++20c++-concepts

How to test concepts retroactively based on post-defined overloads


#include <iostream>
#include <utility>

template <typename T>
concept Printable = requires(const T &x) {
    { std::cout << x } -> std::same_as<std::ostream &>;
};

template <Printable T1, Printable T2>
std::ostream &operator<<(std::ostream &os, const std::pair<T1, T2> &x) {
    return os << "(" << x.first << ", " << x.second << ")";
}

static_assert(Printable<std::pair<int, int>>); // fails because 'std::pair<int, int>' does not satisfy 'Printable'

A concept Printable indicates that a type can be printed to standard output. std::pair<T1, T2> is expected to be Printable whenever T1 and T2 are both Printable.

However, static_assert(Printable<std::pair<int, int>>) fails. Why and how do I fix it?

More precisely, what I want to do is:

#include <iostream>
#include <utility>
#include <vector>

template <typename T>
concept Printable = requires(const T &x) {
    { std::cout << x } -> std::same_as<std::ostream &>;
};

template <Printable T1, Printable T2>
std::ostream &operator<<(std::ostream &os, const std::pair<T1, T2> &x) {
    return os << "(" << x.first << ", " << x.second << ")";
}

template <Printable T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
    for (const auto &e : v) os << e;
    return os;
}

// I want these assertions all to succeed.
static_assert(Printable<std::pair<int, std::vector<int>>>);
static_assert(Printable<std::vector<std::pair<int, int>>>);
static_assert(Printable<std::vector<std::vector<std::vector<int>>>>);

Solution

  • Good idea, but probably impossible.

    Make your own types to wrap std ones, then overload the operators for your types.

    Why?

    1. Modifying std classes behavior would be limited to overloads in std namespace, and you cannot extend it.
    2. Modifying the behavior of external classes is bad, in general, and some other communities will label it as "monkey patching".
    3. Your code will be way more legible if you give an actual name to (the type of) your vector of pairs.
    4. By separating the interface and the implementation you will be more free to change details without breaking your own client code down the road.
    5. Your own classes will be way easier to be unit tested.