Home > Net >  Resolving circular dependency between concept and constrained template function
Resolving circular dependency between concept and constrained template function

Time:07-16

I am trying to learn more about concepts. I ran into some problems with circular dependencies between concepts and constrained template functions, and I've reproduced these errors in a simple example.


I have a concept, Printable, that I want to be satisfied if and only if operator<< is defined on a type. I also have an overload of operator<< on vectors of printable types.

To my surprise, std::vector<int> is not considered Printable, even though operator<< works on it.


#include <iostream>
#include <vector>

template <class T>
concept Printable = requires(std::ostream& out, T a) {
    out << a;
};

template <Printable T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& vec) {
    out << '[';

    for (std::size_t i {}; i < vec.size(); i  ) {
        out << vec[i];
        if (i < vec.size() - 1) {
            out << ", ";
        }
    }

    return out << ']';
}


static_assert(Printable<int>); // This works as expected.
static_assert(Printable<std::vector<int>>); // This fails.

int main() { 
    std::vector<int> vec {1, 2, 3, 4};
    std::cout << vec << '\n'; // This works as expected.
}

This fails on Clang 14.0.6_1 with the following message:

stack_overflow/problem.cpp:26:1: error: static_assert failed
static_assert(Printable<std::vector<int>>); // This fails.
^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~
stack_overflow/problem.cpp:26:15: note: because 'std::vector<int>' does not satisfy 'Printable'
static_assert(Printable<std::vector<int>>); // This fails.
              ^
stack_overflow/problem.cpp:7:9: note: because 'out << a' would be invalid: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
    out << a;
        ^
1 error generated.

So my question is: what can I do to make std::vector<T> considered Printable if T is Printable?


Notes:

  • I believe this compiles fine as is with g , but I recently screwed up my setup for GCC so I cannot confirm this at the moment. If this is true, I would love to know why it works for g but not clang .

    • Update: Barry's comment reminded me that Compiler Explorer exists. I can now confirm that the above code compiles on g but not on clang . I still am curious about why this difference exists.
  • I believe I need to put the operator overload above the declaration of Printable. If I do this and remove the constraint, the code compiles fine. However, I want to keep the Printable constraint if possible, as I believe keeping constraints like this will simplify error messages in the future.

CodePudding user response:

To my surprise, std::vector<int> is not considered Printable, even though operator<< works on it.

It doesn't. Not really anyway. When you say std::cout << x; works what you really mean is that you can write that expression from wherever and that works - "from wherever" including in the definition of Printable. And in the definition of Printable, it... doesn't work. Unqualified lookup doesn't find it and argument-dependent lookup doesn't find it either. The same will likely be true in most other contexts, unless you carefully add a using operator<<; in the appropriate place(s).

You could attempt to move the declaration of operator<< forward, so that the concept definition can see it - but that still wouldn't ultimately resolve the issue of any other code actually being able to call that operator. It can't really work unless this operator is declared in namespace std. And you're not allowed to add it in there.

But if you could, then this would work fine:

namespace std {
    template <class T>
    concept Printable = requires (ostream os, T const var) { os << var; }

    template <Printable T>
    ostream& operator<<(ostream&, vector<T> const&) { ... }
}

Or just use {fmt}

CodePudding user response:

After some consideration of Barry's answer, I continued experimenting and found something that works:



#include <iostream>
#include <vector>

template <class T>
void print(std::ostream& out, T a) {
    out << a;
}

template <class T>
concept Printable = requires(std::ostream& out, T a) {
    print(out, a);
};

template <Printable T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& vec) {
    out << '[';

    for (std::size_t i {}; i < vec.size(); i  ) {
        out << vec[i];
        if (i < vec.size() - 1) {
            out << ", ";
        }
    }

    return out << ']';
}


static_assert(Printable<int>); // This works as expected.
static_assert(Printable<std::vector<int>>); // This works now.

int main() { 
    std::vector<int> vec {1, 2, 3, 4};
    std::cout << vec << '\n'; // This works as expected.

    // And to really prove it, this works too!
    std::vector<std::vector<int>> vec_of_vecs {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    std::cout << vec_of_vecs << '\n';
}

Clang compiles this, and the program outputs:

[1, 2, 3, 4]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

As you can see, the trick is to have Printable rely on another function print().

If I'm being honest, I still don't have a good way to explain why delegating the lookup of operator<< to another function helps here, but it does. I'd love if someone could help explain this.

  • Related