【发布时间】:2026-01-22 08:30:02
【问题描述】:
在使用可变参数模板时,遵循this SO question(注意:不是必须去那里回答这个问题),对于以下重载的模板,我遇到了 clang (3.8) 和 g++ (6.1) 的不同行为功能:
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <template <typename...> class PACK_A,
template <typename...> class PACK_B, typename... Ts1, typename... Ts2>
constexpr bool starts_with(PACK_A<Ts1..., Ts2...>, PACK_B<Ts1...>) {
return true;
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(),
pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int>()) << std::endl;
}
代码:http://coliru.stacked-crooked.com/a/b62fa93ea88fa25b
输出
|---|-----------------------------------------------------------------------------|
| # |starts_with(a, b) | expected | clang (3.8) | g++ (6.1) |
|---|-----------------------------------|-------------|-------------|-------------|
| 1 |a: pack<int, float, double>() | false | false | false |
| |b: pack<float, int, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 2 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, double, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 3 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 4 |a: pack<int, float, double>() | true | true | false |
| |b: pack<int, float, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 5 |a: pack<int, float, double>() | true | false | false |
| |b: pack<int>() | | | |
|---|-----------------------------------------------------------------------------|
最后两种情况(4 和 5)存在问题:我对 更专业的模板 的期望是否错误?如果是这样,在情况 4、clang 或 g++ 中谁是正确的? (请注意,代码编译时没有任何错误或警告,但结果不同)。
为了自己回答这个问题,我多次检查了规范 (14.5.6.2 Partial ordering of function templates) 和 cppreference 中的“更专业”的规则——似乎更专业的规则会给出我期望的结果(一个如果不是,可能会出现歧义错误,但也不是这种情况)。那么,我在这里缺少什么?
等等(1):请不要急着把赫伯·萨特的“prefer not to overload templates”和他的template methods quiz带来。这些当然很重要,但是该语言仍然允许模板重载! (这确实是你不应该重载模板的一个重点——在某些极端情况下,它可能会混淆两个不同的编译器,或者混淆程序员。但问题不在于是否使用它,它是:如果你使用它,正确的行为是什么?)。
等等(2):请不要急于带来其他可能的解决方案。肯定有的。这里有两个:one with inner struct 和 another with inner static methods。两者都是合适的解决方案,都按预期工作,但关于上述模板重载行为的问题仍然存在。
【问题讨论】:
-
我怀疑结果是由于
PACK_A<Ts1..., Ts2...>。编译器应该无法推断出第一个参数包Ts1...,因为它不在模板的末尾。因此它应该是空的,因此永远不应该选择重载。我不知道clang怎么会在案例4中选择它。 -
作为@W.F.说,标准告诉我们 “如果 P 的模板参数列表包含不是最后一个模板参数的包扩展,则整个模板参数列表是非推导上下文。” (14.8. 2.5/8),所以 g++ 似乎是对的,也是错的。
-
确认,将签名更改为
template <typename... Ts1, typename... Ts2 > constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<Ts1...>)在gcc中修复它。 -
@AmirKirsh
Ts...从你的例子中没有推断出来。它们在模板化结构被专门化时是已知的。这是这里最关键的区别。由于Ts...不是在那里推导出来的,它可以按原样放置在那里。 -
什么?您应该重载函数模板。你不应该专门化他们。
标签: c++ templates c++11 variadic-templates overloading