【问题标题】:Ambiguous overload - partial function template ordering with parameter packs模糊重载 - 带有参数包的部分函数模板排序
【发布时间】:2017-04-24 03:20:36
【问题描述】:

考虑以下人为设计的代码:

template <class... > struct pack { };

template <class R, class T, class... Args>
int foo(pack<T>, Args...)
{
    return sizeof(R);
}

template <class R, class T, class... Ts, class... Args>
int foo(pack<T, Ts...>, Args... args)
{
    return foo<T>(pack<Ts...>{}, args...);
}

int main() {
    // gcc: OK, clang: ambiguous
    foo<int>(pack<int>{});

    // gcc: ambiguous, clang: ambiguous
    foo<int>(pack<int>{}, 0);
}

如果将第二个重载更改为采用至少 2 种类型的包而不是至少一种类型的包,则 gcc 和 clang 都接受这两个调用:

template <class R, class T, class T2, class... Ts, class... Args>
int foo(pack<T, T2, Ts...>, Args... args)
{
    return foo<T>(pack<T2, Ts...>{}, args...);
}

如果将非推导模板参数移动到推导模板参数,则:

template <class... > struct pack { };

template <class R, class T, class... Args>
int foo(pack<R>, pack<T>, Args...)
{
    return sizeof(R);
}

template <class R, class T, class... Ts, class... Args>
int foo(pack<R>, pack<T, Ts...>, Args... args)
{
    return foo(pack<T>{}, pack<Ts...>{}, args...);
}

int main() {
    // gcc ok with both, clang rejects both as ambiguous
    foo(pack<int>{}, pack<int>{});
    foo(pack<int>{}, pack<int>{}, 0);
}

我希望在每个版本中所有调用都正常。上述代码示例的预期结果是什么?

【问题讨论】:

  • 我希望每个版本的所有调用都正常。 为什么? (我没有。)
  • @Walter 因为第一个重载比第二个更专业。

标签: c++ templates gcc clang language-lawyer


【解决方案1】:

我现在相信 clang 拒绝是正确的,而 gcc 接受它所接受的那些形式是不正确的。这是一个简化的示例:

template <class...> struct pack { };

// (1)
template <class T>
void foo(pack<T> ) { }

// (2)
template <class T, class... Ts>
void foo(pack<T, Ts...> ) { }

int main() {
    foo(pack<int>{});
}

两个重载都是有效的,从 (1) 推导出 (2) 直接成功。唯一的问题是我们能否从 (2) 中推导出 (1)。我最初不认为...但是 [temp.deduct.type]/9 状态:

如果P 的表单包含&lt;T&gt;&lt;i&gt;,则P 的相应模板参数列表的每个参数Pi 是与A的对应模板实参列表的对应实参Ai比较。 [...] 在部分排序期间 (14.8.2.4),如果 Ai 最初是一个包扩展:
— 如果P 不包含对应于 Ai 的模板参数,则忽略 Ai

所以当我们为&lt;T, Ts...&gt;(比如&lt;U, Xs...&gt;)合成类型时,我们推导出T=U,然后没有模板参数对应于包扩展Xs...,所以我们忽略它。所有未被忽略的模板参数模板推导成功,所以我们认为从(2)推导(1)是成功的。

由于推导在两个方向上都成功,因此没有一个函数模板被认为比另一个更专业,并且调用应该是模棱两可的。


我还没有提交错误报告,等待社区的确认。

【讨论】:

  • 我同意您的分析,但是...更多数据点:C++17 严格模式下的 EDG 4.11 和 MSVC 15 RC 同意 GCC,选择 #1。最新的 WP (N4618) 通过P0519 收到了一些与此相关的修复,但它们不适用于这种情况。 可以在这里应用的是对CWG1432 的修复,但他们没有解决这个问题(疏忽?)。根据 1432 决议的措辞方式,它可以使您的示例明确......或不明确。可能 GCC、EDG 和 MSVC 已经有这种解决方案的临时实现。
  • 此外,我认为最新的修复程序通过渲染 DR 中的最后一个示例(int f(T*...)int f(const T&amp;))对函数调用没有歧义,但对于获取的地址却有歧义函数模板或函数声明的推导(因为在后一种情况下,整个函数类型用于PA,因此对 [temp.deduct.partial]/8 的修复不适用)。我不确定我们是否可以责怪糟糕的编译器。
  • @bogdan 呃……为什么我们要不断地在情况中添加这些超具体的例外,而不是仅仅让规则本身更合理?
  • @bogdan 提交gcc 78753 并开始a thread。我也不明白为什么应该解决 f(T*...)f(T const&amp;) 的案例 - 这似乎是教科书,两者都不比另一个更专业。
  • 我希望你能开始一个线程:-)。让我们看看这是怎么回事。当使用指针调用时,我会直观地说您希望选择 f(T*...)(它是“专门用于指针”,对吗?),并且当前的分辨率可以用于函数调用。
【解决方案2】:

我们先把问题简单化考虑

template <class...> struct pack {};

template <class T>
void foo(pack<T>) {}

template <class T, class... Ts>
void foo(pack<T,Ts...>) {}

int main() {
  foo(pack<int>{});
}

哪个clang抱怨并拒绝编译,声称有 void foo(pack&lt;T&gt;) [with T=int] 和 void foo(pack&lt;T,Ts...&gt;) [with T=int, Ts=&lt;&gt;] 之间的歧义。像这样的情况可以使用partial ordering of overloaded function templates 解决,它本质上是试图找到最专业的重载。

在手头的情况下,我认为以下适用:

在平局的情况下,如果一个函数模板有一个尾随参数包而另一个没有,则认为带有省略参数的模板比带有空参数包的模板更专业。强>

因此,似乎应该首选第一个,而 clang 是错误的。

【讨论】:

  • 这里没有尾随参数包。
  • @Barry 因为是推断出来的,对吧?我的意思是,它可以重写为template &lt;class... Ts, class T&gt; 并且仍然有效。我错了吗?
  • @Barry 那是什么?
  • @Walter template &lt;class T, class... Ts&gt; void foo(T, Ts...)
  • @Barry 如果可以超过您自己的权威来支持这一点,我很乐意接受它作为我最近关于此问题的相关问题的答案。
猜你喜欢
  • 2016-01-30
  • 2015-02-18
  • 1970-01-01
  • 2012-04-10
  • 1970-01-01
  • 2021-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多