解析requires表达式中成员函数参数重载的底层机制
C++ Concept 底层机制答疑:转换运算符评估与重载冲突问题
问题背景代码
#include <string> #include <string_view> template<typename T> concept has_set = requires(T t) { { t.set( [](){ struct { operator std::string_view(); operator std::string(); operator char *(); } s; return s; }() ) } -> std::same_as<void>; }; struct Foo {}; static_assert(!has_set<Foo>); struct Bar { void set(std::string_view); }; static_assert(has_set<Bar>); struct Baz { void set(int i); void set(std::string) const noexcept; }; static_assert(has_set<Baz>); struct Bazz { void set(char *); }; static_assert(has_set<Bazz>); struct Bazzz { void set(char *); void set(std::string); }; // 为何此处编译失败? // static_assert(has_set<Bazzz>);
问题解答
1. requires表达式中实参转换运算符的评估逻辑
在concept的requires表达式检查t.set(...)合法性时,编译器会执行标准的重载决议流程:
- 先枚举
T类型中所有名为set的成员函数重载; - 对每个重载,尝试将
set的实参(匿名结构体对象)转换为该重载的参数类型。此时,匿名结构体的三个转换运算符(std::string_view/std::string/char*)会被逐一评估,判断是否存在可行的转换路径,让实参匹配当前重载的形参; - 这个评估是concept验证的核心:只有存在至少一个重载能通过合法转换完成调用时,
requires表达式的条件才会成立。
本质上,concept复用了C++函数调用的基本规则——函数调用合法性依赖于实参到形参的转换可行性,因此编译器必须评估这些转换运算符是否能产生匹配的类型,以此完成重载决议的校验。
2. Bazzz的重载集导致concept验证失败的原因
Bazzz有两个set重载:void set(char*)和void set(std::string)。当编译器对它执行requires表达式的重载决议时:
- 匿名结构体可通过
operator char*()转换为char*,匹配第一个重载; - 同时也可通过
operator std::string()转换为std::string,匹配第二个重载。
此时重载决议出现二义性:两条转换路径都是合法的,且用户自定义转换的优先级一致,编译器无法确定应该选择哪一个重载调用。
而requires表达式要求整个函数调用必须无歧义且合法,一旦重载决议因二义性失败,requires的条件就不满足,最终导致has_set<Bazzz>的concept验证失败。
内容的提问来源于Stack Exchange,提问作者Markus W.
相关产品推荐
相关产品推荐

