为何用于检查的表达式无法编译时,对应的C++概念仍判定为true?
这个问题其实戳中了C++概念(concept)检查逻辑里一个很容易被忽略的点——概念检查的是表达式是否“潜在可行”,而不是直接编译执行这个表达式。咱们一步步拆解来看:
首先看你写的这段无法编译的代码:
std::get<char>(std::tuple<int, double>{});
它编译失败的原因很明确:std::get有两个核心重载:
- 一个是按索引的非类型模板参数版本(比如
std::get<0>(t)); - 另一个是按元素类型的模板参数版本,用来提取tuple中指定类型的元素。
当你指定<char>作为模板参数时:
- 按索引的版本直接被排除(因为
char是类型,不是合法的非类型模板参数值); - 按类型的版本会尝试实例化,但你的tuple里根本没有
char类型的元素,而且这个重载要求目标类型在tuple里恰好出现一次,所以实例化失败,最终整个表达式编译报错。
那为什么把这段代码放到concept的requires里,就会被判定为可行呢?
这是因为在requires表达式的检查阶段,编译器只会做重载解析,不会去实例化模板的具体实现。换句话说,它只会判断“有没有能匹配这个调用的重载函数存在”,而不会去验证这个重载实例化后能不能正常工作。
回到你的例子,std::get<char>对应的按类型重载确实是存在的,所以重载解析能找到这个候选函数,requires表达式就认为这个表达式是“潜在可行”的,直接返回true——根本不会走到实例化那一步,自然也就不会发现tuple里没有char的问题。
如果想要正确检查tuple中是否包含char类型,你需要让requires表达式触发对std::get<char>的实例化检查,比如加上返回类型的约束:
#include <tuple> #include <concepts> template<typename V> concept HasChar = requires (std::tuple<int, double> t) { { std::get<char>(t) } -> std::same_as<char&>; };
这里的-> std::same_as<char&>会要求编译器确定std::get<char>(t)的返回类型,这就必须实例化std::get的按类型重载。此时编译器会发现tuple里没有char元素,替换失败,concept就会正确返回false了。
另外还有一种更直接的方式,利用tuple的类型特性来检查元素是否存在:
#include <tuple> #include <type_traits> template<typename TargetType, typename Tuple> concept HasType = []<std::size_t... Is>(std::index_sequence<Is...>) { return (std::is_same_v<std::tuple_element_t<Is, Tuple>, TargetType> || ...); }(std::make_index_sequence<std::tuple_size_v<Tuple>>{}); // 使用示例: static_assert(!HasType<char, std::tuple<int, double>>);
这种方式跳过了std::get的调用,直接通过tuple的类型元编程来判断,也能达到目的。
备注:内容来源于stack exchange,提问作者wcobalt

