C++20下模板重载函数编译失败:模板参数推导问题求助
C++20模板重载函数编译失败解决方案
问题背景
代码在C14/C17下可正常编译,切换到C++20后编译失败,报错找不到匹配的_add_sql_where_op_str重载函数,且无法推导模板参数。
相关代码
重载函数1
template<size_t I, UCHAR Operator, class T, class = std::enable_if_t<Operator == SQL_COMP_OP_IN || Operator == SQL_COMP_OP_NOT_IN>> auto _add_sql_where_op_str(CIT2String& strSqlStatement, T&& select, typename base_t::template overload_priority<0>) -> std::void_t<decltype(std::size(std::forward<T>(select))) , decltype(std::begin(std::forward<T>(select))) , decltype(std::end(std::forward<T>(select))) , decltype(std::begin(std::forward<T>(select)) != std::end(std::forward<T>(select))) > { CIT2String ins((ULONG)std::size(std::forward<T>(select)) * 20); CIT2String in(20); ins += '('; bool fFirst = true; for (auto&& elem : std::forward<T>(select)) { static_assert(can_make_du<field_t<I>, decltype(elem)>::value, "TableSerialiser: Elements of iterable range are of incorrect type."); if (!fFirst) ins += ','; else fFirst = false; SelStrMakeFieldData(this->fldinfo_t::dwFieldIDs[I], in.GetPointer(), make_du<field_t<I>>(std::forward<decltype(elem)>(elem)).GetPointer()); ins += in; } ins += ')'; strSqlStatement.GetString() = SelStrAddConditionForField(strSqlStatement.GetString(), this->fldinfo_t::dwFieldIDs[I], Operator, SQL_COMP_OP_TYPE_LITERAL, ins.GetPointer()); }
重载函数2
template<size_t I, UCHAR Operator, class T, class = std::enable_if_t<Operator == SQL_COMP_OP_IN || Operator == SQL_COMP_OP_NOT_IN>> void _add_sql_where_op_str(CIT2String& strSqlStatement, T&& select, typename base_t::template overload_priority<1>) { if constexpr (Operator == SQL_COMP_OP_IN) static_assert(false, "TableSerialiser: Select using in operator must be an iterable range."); else static_assert(false, "TableSerialiser: Select using not-in operator must be an iterable range."); }
重载函数3
template<size_t I, UCHAR Operator, class T, class = std::enable_if_t<Operator != SQL_COMP_OP_IN && Operator != SQL_COMP_OP_NOT_IN>, void* = 0> void _add_sql_where_op_str(CIT2String& strSqlStatement, T&& select, typename base_t::template overload_priority<0>) { static_assert(can_make_du<field_t<I>, T&&>::value, "TableSerialiser: Select is of incorrect type."); strSqlStatement.GetString() = SelStrAddConditionForField(strSqlStatement.GetString(), TableInfos<TableID>::field_infos::dwFieldIDs[I], Operator, SQL_COMP_OP_TYPE_AUTOMATIC, make_du<field_t<I>>(std::forward<T>(select)).GetPointer()); }
调用代码
template<size_t I, UCHAR Operator, class T> void _add_sql_where_or_str(CIT2String& strSqlStatement, bool& fFirst, T&& select) { if (!fFirst) strSqlStatement += " OR "; else fFirst = false; _add_sql_where_op_str<I, Operator>(strSqlStatement, std::forward<T>(select), typename base_t::template priority_tag({})); }
问题原因
- 类型不匹配:调用时传递的
priority_tag{}与重载函数参数的overload_priority<N>类型不一致,C++20对模板参数推导的非推导上下文处理更严格,导致无法匹配重载。 - SFINAE规则变化:C++20中
std::void_t的SFINAE行为调整,原返回值约束的写法可能无法正确触发重载决议。 - 模板参数推导歧义:部分模板参数的约束方式在C++20下导致推导失败。
修复方案
1. 统一优先级标签类型
修改调用代码,传递与重载函数参数匹配的overload_priority<0>类型:
template<size_t I, UCHAR Operator, class T> void _add_sql_where_or_str(CIT2String& strSqlStatement, bool& fFirst, T&& select) { if (!fFirst) strSqlStatement += " OR "; else fFirst = false; // 使用与重载函数匹配的overload_priority<0> _add_sql_where_op_str<I, Operator>(strSqlStatement, std::forward<T>(select), typename base_t::template overload_priority<0>{}); }
2. 改用C++20 requires表达式优化约束
将原std::enable_if_t和std::void_t的约束替换为更清晰的requires表达式,避免SFINAE歧义:
修改重载函数1
template<size_t I, UCHAR Operator, class T> // 先约束Operator类型,再约束T的可迭代性 requires (Operator == SQL_COMP_OP_IN || Operator == SQL_COMP_OP_NOT_IN) && requires(T&& t) { std::size(std::forward<T>(t)); std::begin(std::forward<T>(t)); std::end(std::forward<T>(t)); std::begin(std::forward<T>(t)) != std::end(std::forward<T>(t)); } void _add_sql_where_op_str(CIT2String& strSqlStatement, T&& select, typename base_t::template overload_priority<0>) { // 函数体保持不变 }
修改重载函数3
template<size_t I, UCHAR Operator, class T> requires (Operator != SQL_COMP_OP_IN && Operator != SQL_COMP_OP_NOT_IN) void _add_sql_where_op_str(CIT2String& strSqlStatement, T&& select, typename base_t::template overload_priority<0>) { static_assert(can_make_du<field_t<I>, T&&>::value, "TableSerialiser: Select is of incorrect type."); strSqlStatement.GetString() = SelStrAddConditionForField(strSqlStatement.GetString(), TableInfos<TableID>::field_infos::dwFieldIDs[I], Operator, SQL_COMP_OP_TYPE_AUTOMATIC, make_du<field_t<I>>(std::forward<T>(select)).GetPointer()); }
3. 移除冗余模板参数
重载函数3中的void* = 0是C14时期用于避免重载歧义的技巧,C20下改用requires后可直接移除,简化模板定义。
验证效果
修改后代码可在C20标准下正常编译,同时保持对C17的兼容性(若保留enable_if和void_t的写法,需确保编译器支持C++17及以上)。
内容的提问来源于stack exchange,提问作者PHENIX
相关产品推荐
相关产品推荐

