【问题标题】:Ambiguous call when recursively calling variadic template function overload递归调用可变参数模板函数重载时的不明确调用
【发布时间】:2017-02-06 08:14:21
【问题描述】:

考虑这段代码:

template<typename FirstArg>
void foo()
{
}

template<typename FirstArg, typename... RestOfArgs>
void foo()
{
    foo<RestOfArgs...>();
}

int main()
{
    foo<int, int, int>();
    return 0;
}

RestOfArgs只有一个元素({int})时,由于不明确的调用foo&lt;RestOfArgs...&gt;();而无法编译。

但这编译没有错误:

template<typename FirstArg>
void foo(FirstArg x)
{
}

template<typename FirstArg, typename... RestOfArgs>
void foo(FirstArg x, RestOfArgs... y)
{
    foo(y...);
}

int main()
{
    foo<int, int, int>(5, 6, 7);
    return 0;
}

为什么第一种情况有歧义?

为什么第二种情况没有歧义?

【问题讨论】:

  • 我的假设是它与函数的签名是它的参数这一事实有关,&lt;int&gt;&lt;int&gt; 在一种情况下是无法区分的,但在另一种情况下是为什么。跨度>
  • @Jarod42 我看了但还是不知道我的问题的答案。
  • @rubix_addict:我同意这从给定的链接中并不明显。我在答案中强调了这一点。

标签: c++ variadic-templates overload-resolution


【解决方案1】:

The answer by @ZangMingJie 回答了您在代码中观察到的行为差异。

我发现通过以下更改更容易理解名称解析:

template<typename FirstArg>
void foo()
{
    printf("1\n");
}

template<typename FirstArg, typename SecondArg, typename... RestOfArgs>
void foo()
{
    printf("2\n");
    foo<SecondArg, RestOfArgs...>();
}

int main()
{
    foo<int, int, int>();
    return 0;
}

当使用两个或多个模板参数时,将调用第二个函数。当有一个模板参数时,第一个函数被调用。

【讨论】:

  • 这有效并解决了歧义问题,但我仍然不知道为什么。我原来的问题还没有回答。
  • @rubix_addict,我认为 Zang 的回答确实解释了这个问题。
  • @RSahu:我不这么认为,正如他解释的那样,SFINAE 关于函数定义的内容会被使用,这是错误的:-/
【解决方案2】:

Function template overloading

有很多规则可以判断哪些模板函数更专业(根据给定的参数)。

要点

template<typename> void foo();
template<typename, typename...> void foo();

foo&lt;int&gt;() 不明确,但不是

template<typename T> void foo(T);
template<typename T, typename... Ts> void foo(T, Ts...);

foo(42) 如下:

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

【讨论】:

  • 这句话不是暗示这两种情况都不应该有任何歧义吗?好吧,“尾随参数包”有点……模棱两可(双关语)-它可以适用于“尾随模板参数包”和“尾随函数参数包”。
  • 我同意 IMO 不够清楚,但“尾随”不适用于 template &lt;typename Leading, typename Ts..., typename Trailing&gt; void f(Leading, std::tuple&lt;Ts...&gt;, Trailing)template &lt;typename B, typename A&gt; void f(A, std::tuple&lt;&gt;, B)。这与您的情况相似。
  • 但在我的两种情况下,参数包都是“尾随”
  • 我的意思是尾随参数适用于函数的参数,而不是模板的参数(这会与上面的例子有些不一致)。
【解决方案3】:

为什么第一种情况有歧义?

RestOfArgs 可以为空。

所以foo&lt;int&gt; 可以实例化为:

template<int>
void foo()
{
}

template<int,>
void foo()
{
    foo<>();
}

两者都会编译,所以它是模棱两可的。

其实foo&lt;&gt;()不会编译,但是下次实例化就失败了,所以没关系。

为什么第二种情况没有歧义?

foo&lt;int&gt;(7) 可以实例化为:

template<int>
void foo(int 7)
{
}

template<int>
void foo(int 7)
{
    foo();
}

但是第二个是错误的,因为没有foo不带参数,所以唯一的候选者是第一个,所以不会有歧义

【讨论】:

  • 您介意解释一下this example 吗?我已经删除了 foo 调用,它仍然可以编译。
  • @yeputons 确定,没有递归调用,只使用 VAARG 模板。
  • @yeputons,不要将 SO 用作咨询服务。这是一个很好的回答你的问题。如果您还有其他相关问题,请发布另一个问题。
  • @ZangMingJie 所以在第二种情况下,由于 SFINAE,第二个 foo 重载从重载集中删除,对吧?
  • @rubix_addict 我也这么认为(请参阅我的 cmets 到这里的两个答案),但看起来情况并非如此(请参阅我的示例)。如果您在第二次重载中省略了对 foo 的调用,它仍然会在指定参数(但未使用)时编译,但在没有指定参数时不会编译。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多