模糊过载 - 使用参数包进行部分功能模板排序

时间:2016-12-08 19:05:56

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

考虑以下设计的代码:

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);
}

我希望在每个版本的调用都可以。上述代码示例的预期结果是什么?

2 个答案:

答案 0 :(得分:4)

我现在相信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的表单包含<T><i>,则相应模板参数的每个参数P i P的列表与A的相应模板参数列表的相应参数A i 进行比较。 [...]在部分订购(14.8.2.4)期间,如果A i 最初是包扩展:
   - 如果P不包含对应于A i 的模板参数,则忽略A i ;

因此,当我们合成<T, Ts...>的类型(比如<U, Xs...>)时,我们推导出T=U,然后没有与包扩展Xs...对应的模板参数,所以我们忽略它。所有未忽略的模板参数都在模板推导中成功,因此我们考虑从(2)中推导出(1)为成功。

由于演绎在两个方向都成功,因此功能模板都不会被认为比另一个更专业,并且调用应该是模糊的。

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

答案 1 :(得分:-1)

让我们首先简化问题并考虑

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<T>) [与T=int]和void foo(pack<T,Ts...>) [与T=intTs=<>]之间存在歧义。像这样的情况使用partial ordering of overloaded function templates来解决,它基本上试图找到最专业的重载。

就目前的情况而言,我认为以下情况适用:

如果一个平局,如果一个函数模板有一个尾随参数包而另一个没有,那么带有省略参数的那个被认为比具有空参数包的那个更专业。

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