GCC中成员函数未隐藏基类类型别名?兼询解决方案
GCC对C++20约束的解析bug及兼容解决方案
#include <utility> struct base { using get = void; }; struct type_a : base { int get(int i) const { return i; } }; struct type_b : base { }; template <typename T, typename... Args> auto get_or_default(const T& obj, Args&&... args) -> decltype(obj.get(std::forward<Args>(args)...)) { return obj.get(std::forward<Args>(args)...); } template <typename T> requires requires { typename T::get; } // 预期该约束对type_a不成立 int get_or_default(const T&) { return -1; } int main() { const type_a a; const int i0 = get_or_default(a, 42); const int i1 = get_or_default(a); // GCC 11.1编译通过,MSVC/Clang报错(符合预期) const type_b b; const int i2 = get_or_default(b); return i0 + i1 + i2; }
问题说明
这段代码的设计意图:
- 第一个
get_or_default重载:调用对象的get成员函数,要求参数匹配函数签名; - 第二个
get_or_default重载:仅当类型T存在嵌套类型T::get时启用,返回默认值-1。
根据C++标准,type_a中的成员函数get会隐藏基类base的嵌套类型别名get,因此typename type_a::get是非法语法——get是成员函数而非类型。因此get_or_default(a)(无参调用)理应编译失败:第一个重载因type_a::get需要参数而不可行,第二个重载因约束不满足而被排除。
实际编译器表现:
- MSVC 14.29和Clang 15.0.0:行为符合预期,报错提示找不到匹配的重载函数;
- GCC 11.1:错误判定
type_a满足第二个重载的约束,导致无参调用编译通过并返回-1。此外,在模板外单独编写requires { typename type_a::get; }时GCC会正确报错,说明模板内的约束解析存在实现bug。
结论
这确实是GCC的bug,属于模板约束上下文里的名字查找逻辑错误,该问题在GCC 12及以上版本中已被修复。
GCC兼容解决方案
如果无法升级GCC版本,可以通过辅助模板更严谨地检测T::get是否为类型,绕过该bug:
#include <utility> #include <type_traits> struct base { using get = void; }; struct type_a : base { int get(int i) const { return i; } }; struct type_b : base { }; // 辅助模板:检测T是否存在合法的嵌套类型get template<typename T, typename = void> struct has_get_type : std::false_type {}; template<typename T> struct has_get_type<T, std::void_t<typename T::get>> : std::true_type {}; template <typename T, typename... Args> auto get_or_default(const T& obj, Args&&... args) -> decltype(obj.get(std::forward<Args>(args)...)) { return obj.get(std::forward<Args>(args)...); } // 使用辅助模板替代直接的requires约束 template <typename T> requires has_get_type<T>::value int get_or_default(const T&) { return -1; } int main() { const type_a a; const int i0 = get_or_default(a, 42); // const int i1 = get_or_default(a); // 现在GCC也会正确报错 const type_b b; const int i2 = get_or_default(b); return i0 + i2; }
该方案通过std::void_t实现的has_get_type辅助模板,更准确地检测T::get是否为合法类型,避免GCC在模板约束上下文里的名字查找错误。
内容的提问来源于stack exchange,提问作者Daniel Eiband
相关产品推荐
相关产品推荐

