由于非类型参数专用化而导致的模棱两可的模板实例化

Ambiguous Template Instantiation due to Non-Type Parameter Specialization

提问人:ssmaniot 提问时间:5/16/2023 更新时间:5/16/2023 访问量:86

问:

我正在尝试使用 TMP 实现列表及其操作。

以下代码无法编译,无法实例化(最后一行):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

g++ 编译器给出以下错误,clang 提供相同的错误。我不明白为什么实例化是模棱两可的,因为我提供了一个索引为 0 的专业化:

main.cpp: In instantiation of ‘struct Insert<-1, 1, List<1, 4> >’:
main.cpp:27:8:   recursively required from ‘struct Insert<-1, 2, List<3, 1, 4> >’
main.cpp:27:8:   required from ‘struct Insert<-1, 3, List<6, 3, 1, 4> >’
main.cpp:21:7:   required by substitution of ‘template<int Em, int Idx, class TList> using Insert_t = typename Insert::type [with int Em = -1; int Idx = 3; TList = List<6, 3, 1, 4>]’
main.cpp:30:61:   required from here
main.cpp:27:8: error: ambiguous template instantiation for ‘struct Insert<-1, 0, List<4> >’
  27 | struct Insert<Em, Idx, List<H, I...>> : Append<H, Insert_t<Em, Idx-1, List<I...>>> {};
     |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:24:8: note: candidates are: ‘template<int Em, int ...I> struct Insert<Em, 0, List<I ...> > [with int Em = -1; int ...I = {4}]’
  24 | struct Insert<Em, 0, List<I...>> : Append<Em, List<I...>> {};
     |        ^~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:27:8: 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 = {}]’
  27 | struct Insert<Em, Idx, List<H, I...>> : Append<H, Insert_t<Em, Idx-1, List<I...>>> {};
     |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:27:8: error: invalid use of incomplete type ‘struct Insert<-1, 0, List<4> >’
main.cpp:18:8: note: declaration of ‘struct Insert<-1, 0, List<4> >’
  18 | struct Insert;
     |        ^~~~~~
main.cpp: In instantiation of ‘struct Insert<-1, 2, List<3, 1, 4> >’:
main.cpp:27:8:   required from ‘struct Insert<-1, 3, List<6, 3, 1, 4> >’
main.cpp:21:7:   required by substitution of ‘template<int Em, int Idx, class TList> using Insert_t = typename Insert::type [with int Em = -1; int Idx = 3; TList = List<6, 3, 1, 4>]’
main.cpp:30:61:   required from here
main.cpp:27:8: error: no type named ‘type’ in ‘struct Insert<-1, 1, List<1, 4> >’
  27 | struct Insert<Em, Idx, List<H, I...>> : Append<H, Insert_t<Em, Idx-1, List<I...>>> {};
     |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp: In instantiation of ‘struct Insert<-1, 3, List<6, 3, 1, 4> >’:
main.cpp:21:7:   required by substitution of ‘template<int Em, int Idx, class TList> using Insert_t = typename Insert::type [with int Em = -1; int Idx = 3; TList = List<6, 3, 1, 4>]’
main.cpp:30:61:   required from here
main.cpp:27:8: error: no type named ‘type’ in ‘struct Insert<-1, 2, List<3, 1, 4> >’
main.cpp: In substitution of ‘template<int Em, int Idx, class TList> using Insert_t = typename Insert::type [with int Em = -1; int Idx = 3; TList = List<6, 3, 1, 4>]’:
main.cpp:30:61:   required from here
main.cpp:21:7: error: no type named ‘type’ in ‘struct Insert<-1, 3, List<6, 3, 1, 4> >’
  21 | using Insert_t = typename Insert<Em, Idx, TList>::type;
     |       ^~~~~~~~
main.cpp:30:20: error: template argument 1 is invalid
  30 | static_assert(std::is_same_v<Insert_t<-1, 3, List<6, 3, 1, 4>>, List<6, 3, 1, -1, 4>>);
     |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我想实现一个 Insert<Em, Idx, TList> 操作,以在 TList 列表的 Idx 位置插入一个新元素 Em。在我的实现中,我提供了一个模板部分专用化,以便结构中提供的类型与 相同。但是,它被认为与其他部分专业化模棱两可。我不明白为什么,因为第一个部分专业化应该是模式限制性的,以及如何解决这个问题。Insert<Em, 0, List<I...>>Append<Em, TList>Insert<Em, Idx, List<H, I...>>

C++ 列表 模板 template-meta-programming

评论

1赞 ALX23z 5/16/2023
为什么你认为第一个一定比另一个更严格?第二个要求列表中的整数序列为非空。您可以利用 SFINEA 限制进行类专精,并在 时禁用第二个。idx == 0
0赞 Paul Sanders 5/16/2023
使用 C++20 轻松操作,请参阅:wandbox.org/permlink/ze446Sc5m8wSGF39(感谢 @ALX23z)
0赞 ALX23z 5/16/2023
@PaulSanders,当它编译时...目前,C++ 标准不支持使用概念来限制类的部分专用化。仅限函数模板。
1赞 fabian 5/16/2023
为什么要为此使用模板?只需实现 constexpr 函数即可。您甚至可以在方法和 C++20: godbolt.org/z/EzbMaGqGz 中的 constexpr 函数之间进行转换
0赞 aschepler 5/16/2023
@ALX23z C++20 [temp.class.spec]/4 “类模板部分专用化可能会受到限制”?

答:

2赞 Jarod42 5/16/2023 #1

你错过了一个案例:

template <int Em, int H, int... I>
struct Insert<Em, 0, List<H, I...>> : Append<Em, List<H, I...>> {};

演示