c++c++20c++-conceptsrequires-expression

Why does this requires expression work as a concept but not directly on a function?


The following fails to compile.

template <typename... U>
requires requires(U... x) { (std::to_string(x) && ...); }
auto to_string(const std::variant<U...>& value) {
    return std::visit([](auto&& value) {
        return std::to_string(std::forward<decltype(value)>(value));
    }, value);
}

int main() {
    std::variant<int, float> v = 42;
    std::cout << to_string(v) << std::endl;
}

https://godbolt.org/z/Wvn6E3PG5

If I convert the direct requires expression into a concept though, it works fine.

template<typename T, typename... U>
concept to_stringable = requires(U... u) { (std::to_string(u) && ...); };

template <to_stringable... U>
auto to_string(const std::variant<U...>& value) {
    return std::visit([](auto&& value) {
        return std::to_string(std::forward<decltype(value)>(value));
    }, value);
}

int main() {
    std::variant<int, float> v = 42;
    std::cout << to_string(v) << std::endl;
}

https://godbolt.org/z/W6znbvTzo


Solution

  • When you have:

    template <to_stringable... U>
    auto to_string(const std::variant<U0, U1>& value) {
    

    This checks if each individual type satisfies to_stringable<T>, so it is essentially equivalent to:

    template <to_stringable U0, to_stringable U1>
    auto to_string(const std::variant<U0, U1>& value) {
    

    And with just one argument T your concept is:

    requires(T t) { (std::to_string(t)); };
    

    However, with more than one argument, it looks like:

    requires(T1 t1, T2 t2) { (std::to_string(t1) && std::to_string(t2)); }
    

    Which doesn't work because you can't && two std::strings, so the constraint is not satisfied.

    What you really want to do is fold over a constraint:

    template <typename... U>
    requires ((requires(U x) { std::to_string(x); }) && ...)
    

    ... Which gcc doesn't seem to implement properly because of a bug but you can get away with:

    template <typename... U>
    requires requires(U... x) { ((void) std::to_string(x), ...); }