GCC下模板模板函数重载歧义问题求助及编译器兼容疑问
问题描述
我编写了一段C++代码,在Clang 17.0.2(Android armv7,NDK版本26.1.10909125)中可正常编译运行:
template <template <typename, typename> typename T> struct CollectIntoAllocatedContainer { // ... }; template <template <typename, typename, typename> typename T> struct CollectIntoAllocatedContainerWithCharTraits { // ... }; template <template <typename, typename> typename T> constexpr CollectIntoAllocatedContainer<T> collect() { return {}; } template <template <typename, typename, typename> typename T> constexpr CollectIntoAllocatedContainerWithCharTraits<T> collect() { return {}; } int main() { collect<std::vector>().dosomething(); collect<std::basic_string>().dosomething(); }
但在GCC 13.3.0中编译时触发歧义错误:
error: call of overloaded ‘collect<template<class _CharT, class _Traits, class _Alloc> class std::__cxx11::basic_string>()’ is ambiguous 78 | collect<std::basic_string>(); | ~~~~~~~~~~~~~~~~~~~~~~~~~~^~ note: candidate: ‘constexpr CollectIntoAllocatedContainer<T> collect() [with T = std::__cxx11::basic_string]’ 64 | constexpr CollectIntoAllocatedContainer<T> collect() | ^~~~~~~ note: candidate: ‘constexpr CollectIntoAllocatedContainerWithCharTraits<T> collect() [with T = std::__cxx11::basic_string]’ 70 | constexpr CollectIntoAllocatedContainerWithCharTraits<T> collect() | ^~~~~~~
我尝试用Concepts做约束,但问题依旧:
template <template <typename, typename> typename T, typename V> concept AllocatedContainer = requires(V x) { typename T<V, std::allocator<V>>; }; template <template <typename, typename, typename> typename T, typename V> concept AllocatedContainerWithCharTraits = requires(V x) { typename T<V, std::char_traits<V>, std::allocator<V>>; }; template <template <typename, typename> typename T> constexpr CollectIntoAllocatedContainer<T> collect() requires AllocatedContainer<T, std::monostate> { return {}; } template <template <typename, typename, typename> typename T> constexpr CollectIntoAllocatedContainerWithCharTraits<T> collect() requires AllocatedContainerWithCharTraits<T, std::monostate> { return {}; }
由于公司基础设施限制无法更换编译器版本,想知道这是GCC的Bug吗?还是Clang错误地允许了此类代码?有没有可行的解决方案?
原因分析
这不是编译器Bug,而是GCC和Clang对模板模板参数匹配规则的处理差异:
std::basic_string是一个接受3个模板参数的模板(_CharT,_Traits,_Alloc,后两个有默认值)。- Clang在匹配模板模板参数时,优先匹配参数数量完全一致的重载;而GCC认为带默认参数的模板可以适配参数数量更少的模板模板参数(比如把3参数模板适配成2参数模板,忽略第三个默认参数),因此两个
collect重载都能匹配,导致歧义。
你之前的Concepts约束没生效,是因为std::basic_string既满足AllocatedContainer(可以只传前两个参数,第三个用默认值),也满足AllocatedContainerWithCharTraits,两个重载依然都符合约束条件,歧义问题没解决。
解决方案
要明确区分两种模板,需要让约束互斥,确保一个模板只能匹配其中一个重载:
方案1:用Concepts排除重叠情况
调整Concepts,让3参数模板的约束明确排除能适配2参数模板的情况:
#include <concepts> #include <memory> #include <string> #include <vector> #include <variant> template <template <typename, typename> typename T, typename V> concept AllocatedContainer = requires(V x) { typename T<V, std::allocator<V>>; }; template <template <typename, typename, typename> typename T, typename V> concept AllocatedContainerWithCharTraits = requires(V x) { typename T<V, std::char_traits<V>, std::allocator<V>>; } && !AllocatedContainer<T, V>; // 关键:排除能适配2参数模板的情况 template <template <typename, typename> typename T> constexpr CollectIntoAllocatedContainer<T> collect() requires AllocatedContainer<T, std::monostate> { return {}; } template <template <typename, typename, typename> typename T> constexpr CollectIntoAllocatedContainerWithCharTraits<T> collect() requires AllocatedContainerWithCharTraits<T, std::monostate> { return {}; }
方案2:显式指定模板参数数量(利用SFINAE)
如果无法使用C++20 Concepts,也可以用SFINAE来区分:
#include <type_traits> #include <memory> #include <string> #include <vector> // 检测模板是否接受2个参数 template <template <typename, typename> typename T, typename V = void> struct is_2arg_template : std::false_type {}; template <template <typename, typename> typename T> struct is_2arg_template<T, std::void_t<typename T<int, std::allocator<int>>>> : std::true_type {}; // 检测模板是否接受3个参数且不能适配2个参数 template <template <typename, typename, typename> typename T, typename V = void> struct is_3arg_only_template : std::false_type {}; template <template <typename, typename, typename> typename T> struct is_3arg_only_template<T, std::void_t<typename T<int, std::char_traits<int>, std::allocator<int>>>> : std::bool_constant<!is_2arg_template<T>::value> {}; template <template <typename, typename> typename T> constexpr std::enable_if_t<is_2arg_template<T>::value, CollectIntoAllocatedContainer<T>> collect() { return {}; } template <template <typename, typename, typename> typename T> constexpr std::enable_if_t<is_3arg_only_template<T>::value, CollectIntoAllocatedContainerWithCharTraits<T>> collect() { return {}; }
方案3:给collect函数加标签参数
最直接的方式是显式区分,调用时指定标签:
struct TwoArgTag {}; struct ThreeArgTag {}; template <template <typename, typename> typename T> constexpr CollectIntoAllocatedContainer<T> collect(TwoArgTag) { return {}; } template <template <typename, typename, typename> typename T> constexpr CollectIntoAllocatedContainerWithCharTraits<T> collect(ThreeArgTag) { return {}; } // 调用方式 int main() { collect<std::vector>(TwoArgTag{}).dosomething(); collect<std::basic_string>(ThreeArgTag{}).dosomething(); }
内容的提问来源于stack exchange,提问作者burbokop
相关产品推荐
相关产品推荐

