为何requires表达式用作concept可行,直接用于函数却无法编译?
为什么直接用requires表达式的模板无法编译,改用concept却可以?
问题描述
下面这段代码无法编译:
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; }
但将直接的requires表达式替换为concept后,代码可以正常运行:
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; }
请问两者的差异原因是什么?
原因解释
1. 第一种写法的核心错误
直接使用的requires表达式里,(std::to_string(x) && ...)是逻辑与折叠表达式,但std::to_string的返回值是std::string类型,C++并没有为std::string定义operator&&(逻辑与运算符)。这导致该requires表达式的约束条件不成立,编译器会判定模板不满足使用要求,不会对其进行实例化。最终在main函数调用to_string(v)时,找不到匹配的函数重载,触发编译错误。
2. 第二种写法的“巧合”生效
你写的concept定义存在设计瑕疵,但编译器的处理逻辑让它意外生效:
- 当用
to_stringable... U作为模板约束时,每个U类型会匹配到concept的第一个模板参数T,而concept的参数包U会被推导为空。 - 根据C++标准,空参数包的逻辑与折叠表达式
(std::to_string(u) && ...)求值为true,因此这个concept的约束总是满足,编译器会正常实例化模板。 - 模板内部的
std::visitlambda中,std::to_string对int和float都有现成的重载,因此代码能正确运行。
正确的约束写法
如果要真正实现“所有变体类型都支持std::to_string”的约束,应该把concept改成针对单个类型的设计:
template<typename T> concept to_stringable = requires(T t) { std::to_string(t); }; 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); }
这个版本的concept会逐一检查每个U类型是否能被std::to_string处理,才是符合设计意图的约束。
内容的提问来源于stack exchange,提问作者Chris_F
相关产品推荐
相关产品推荐

