g++与clang可调用性歧义调用判定差异:谁正确?有无替代方案?
编译器行为正确性分析
首先,Clang 5.0的行为符合C++标准,而GCC 7.2存在Bug。
原因在于C++标准对std::is_invocable trait的明确定义:当且仅当表达式std::declval<T>()(std::declval<Args>()...)作为未求值操作数是良构(well-formed)时,is_invocable_v<T, Args...>才会返回true。而你的functor从两个基类继承了签名相同的operator(),且没有通过using声明消除歧义,此时直接调用该运算符会触发名字查找歧义——编译器无法确定要调用哪个基类的版本,这个表达式属于非法(ill-formed)。
按照标准,这种歧义场景下is_invocable应该返回false,这正是Clang给出的结果(第一个输出值为0)。而GCC错误地将歧义调用判定为合法,返回了true,不符合标准要求。
替代方案:自定义
is_unambiguously_invocable 在等待GCC修复Bug期间,完全可以通过自定义类型 trait is_unambiguously_invocable来准确判断一个调用是否既合法又无歧义。
核心思路是利用SFINAE(替换失败并非错误)特性:尝试编译目标调用表达式,如果因为歧义或其他原因导致编译失败,就返回false;如果编译成功且无歧义,则返回true。
下面是适配你场景的实现:
#include <type_traits> #include <utility> namespace detail { // 优先匹配:如果调用表达式良构且无歧义,返回true_type template <typename Callable, typename... Args> auto test_unambiguous_invoke(int) -> decltype( static_cast<void>(std::declval<Callable>()(std::declval<Args>()...)), std::true_type{} ); // 兜底匹配:如果调用表达式非法(包括歧义),返回false_type template <typename Callable, typename... Args> std::false_type test_unambiguous_invoke(...); } template <typename Callable, typename... Args> struct is_unambiguously_invocable : decltype(detail::test_unambiguous_invoke<Callable, Args...>(0)) {}; // 便捷变量模板 template <typename Callable, typename... Args> inline constexpr bool is_unambiguously_invocable_v = is_unambiguously_invocable<Callable, Args...>::value;
工作原理:
- 当调用
std::declval<Callable>()(std::declval<Args>()...)合法且无歧义时,第一个test_unambiguous_invoke重载会被选中,返回std::true_type。 - 当调用存在歧义(或其他编译错误,比如参数不匹配)时,第一个重载会因为SFINAE被排除,编译器会匹配到接受
...的兜底重载,返回std::false_type。
这个trait会在GCC和Clang上给出一致结果,准确区分“合法无歧义调用”和“非法/歧义调用”。
内容的提问来源于stack exchange,提问作者Vincent
相关产品推荐
相关产品推荐

