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
CodePudding user response:
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::string
s, 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), ...); }
CodePudding user response:
You did not exactly convert it into a concept. This would be an exact conversion into a concept:
template<typename... U>
concept real_to_stringable = requires(U... u) { (std::to_string(u) && ...); };
template <typename ...U>
requires real_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);
}
And it gives the same error. For the same reason: std::to_string
returns a basic_string
. Which does not have a && overload. And therefore, &&ing them all together doesn't compile. So the constraint is not satisfied.
The reason your version of the concept code appears to work is because you didn't do it right. A concept whose first parameter is a type is a "type concept". A type concept can be used in place of a typename in certain circumstances as a shorthand for adding a requires
constraint.
The first template parameter for such a concept is filled in with the type given as an argument to the template being constrained. The other template parameters for the concept are filled in by the other arguments provided at the point of the concept's use. But... you didn't provide any; to_stringable
wasn't given other arguments in the template header. So the U
in your to_stringable
is always an empty set. Which means the constraint is always satisfied.
That is, template<to_stringable ...U>
produces a requires constraint equivalent to requires (to_stringable<U> && ...)
, not requires to_stringable<U...>
.