非类型参数特化导致C++模板实例化歧义问题排查
C++模板元编程:Insert模板特化歧义问题修复
你尝试用模板元编程实现列表的插入操作,但Insert_t在实例化时出现歧义错误,代码如下:
#include <type_traits> template <int... I> struct List; template <int Em, typename TList> struct Append; template <int Em, typename TList> using Append_t = typename Append<Em, TList>::type; template <int Em, int... I> struct Append<Em, List<I...>> { using type = List<Em, I...>; }; template <int Em, int Idx, typename TList> struct Insert; template <int Em, int Idx, typename TList> using Insert_t = typename Insert<Em, Idx, TList>::type; template <int Em, int... I> struct Insert<Em, 0, List<I...>> : Append<Em, List<I...>> {}; template <int Em, int Idx, int H, int... I> struct Insert<Em, Idx, List<H, I...>> : Append<H, Insert_t<Em, Idx-1, List<I...>>> {}; static_assert(std::is_same_v<Insert_t<4, 4, List<0, 1, 2, 3>>, List<0, 1, 2, 3, 4>>); // OK static_assert(std::is_same_v<Insert_t<-1, 3, List<6, 3, 1, 4>>, List<6, 3, 1, -1, 4>>); // Fails
编译器报错核心信息:
error: ambiguous template instantiation for ‘struct Insert<-1, 0, List<4> >’ note: candidates are: ‘template<int Em, int ...I> struct Insert<Em, 0, List<I ...> > [with int Em = -1; int ...I = {4}]’ note: ‘template<int Em, int Idx, int H, int ...I> struct Insert<Em, Idx, List<H, I ...> > [with int Em = -1; int Idx = 0; int H = 4; int ...I = {}]’
问题原因
当实例化Insert<Em, 0, List<H>>(比如Insert<-1,0,List<4>>)时,两个偏特化都能匹配:
- 第一个特化
Insert<Em, 0, List<I...>>:I...会被推导为{4},完全匹配。 - 第二个特化
Insert<Em, Idx, List<H, I...>>:Idx被推导为0,H为4,I...为空,也完全匹配。
C++模板偏特化的匹配规则中,这两个特化没有谁比谁更特化,编译器无法确定选择哪一个,因此产生歧义。
修复方案
给第二个特化添加约束,让它仅在Idx != 0时生效。以下提供两种兼容不同C++版本的实现方式:
方案1:C++11及以上(使用std::enable_if)
#include <type_traits> template <int... I> struct List; template <int Em, typename TList> struct Append; template <int Em, typename TList> using Append_t = typename Append<Em, TList>::type; template <int Em, int... I> struct Append<Em, List<I...>> { using type = List<Em, I...>; }; // 新增默认模板参数用于enable_if约束 template <int Em, int Idx, typename TList, typename = void> struct Insert; template <int Em, int Idx, typename TList> using Insert_t = typename Insert<Em, Idx, TList>::type; template <int Em, int... I> struct Insert<Em, 0, List<I...>> : Append<Em, List<I...>> {}; // 添加Idx != 0的约束 template <int Em, int Idx, int H, int... I> struct Insert<Em, Idx, List<H, I...>, typename std::enable_if<Idx != 0>::type> : Append<H, Insert_t<Em, Idx-1, List<I...>>> {}; static_assert(std::is_same_v<Insert_t<4, 4, List<0, 1, 2, 3>>, List<0, 1, 2, 3, 4>>); // OK static_assert(std::is_same_v<Insert_t<-1, 3, List<6, 3, 1, 4>>, List<6, 3, 1, -1, 4>>); // Now OK
方案2:C++20及以上(使用requires语法)
#include <type_traits> template <int... I> struct List; template <int Em, typename TList> struct Append; template <int Em, typename TList> using Append_t = typename Append<Em, TList>::type; template <int Em, int... I> struct Append<Em, List<I...>> { using type = List<Em, I...>; }; template <int Em, int Idx, typename TList> struct Insert; template <int Em, int Idx, typename TList> using Insert_t = typename Insert<Em, Idx, TList>::type; template <int Em, int... I> struct Insert<Em, 0, List<I...>> : Append<Em, List<I...>> {}; // 添加Idx != 0的requires约束 template <int Em, int Idx, int H, int... I> requires (Idx != 0) struct Insert<Em, Idx, List<H, I...>> : Append<H, Insert_t<Em, Idx-1, List<I...>>> {}; static_assert(std::is_same_v<Insert_t<4, 4, List<0, 1, 2, 3>>, List<0, 1, 2, 3, 4>>); // OK static_assert(std::is_same_v<Insert_t<-1, 3, List<6, 3, 1, 4>>, List<6, 3, 1, -1, 4>>); // Now OK
原理说明
通过添加Idx != 0的约束,当Idx为0时,第二个特化会被排除在候选列表之外,此时只有第一个特化匹配,消除了歧义。递归过程中,当Idx递减到0时,会正确触发第一个特化完成插入操作。
内容的提问来源于stack exchange,提问作者ssmaniot
相关产品推荐
相关产品推荐

