【问题标题】:Template argument deduction when mixing variadic template with C-style variadic function将可变参数模板与 C 样式可变参数函数混合时的模板参数推导
【发布时间】:2021-12-08 01:38:41
【问题描述】:

this answer 的启发,我生成了这段代码,其输出取决于编译器:

template <typename... Args>
constexpr auto foo(Args&& ...args, ...) noexcept {
    return sizeof...(args);
}

constexpr auto bar() noexcept {
    return (&foo<int>)(1, 2);
}

如果使用 GCC 11 编译,bar 调用 foo&lt;int&gt; 并返回 1,而 clang 13 和 MSVC 2019 都推导出 foo&lt;int, int&gt;bar 返回 2。

这是我在 Godbolt 上的沙盒:https://godbolt.org/z/MedvvbzqG

哪个输出是正确的?

编辑:

如果我直接使用return foo&lt;int&gt;(1, 2);,即与

constexpr auto bar() noexcept {
    return foo<int>(1, 2);
}

沙盒更新:https://godbolt.org/z/Wj757sc7b

【问题讨论】:

  • 我会说gcc,因为(&amp;foo&lt;int&gt;)应该先做,所以不能使用后面的推论...
  • 我认为 Clang 和 MSVC 正在为自己的优化而绊倒。获取地址并立即调用常规函数可以内联到......只是调用它。然而,这并不适用于此,因为现在获取地址首先会影响行为。
  • 确实,在混合中添加一个临时变量auto p = &amp;foo&lt;int&gt;; 会使 Clang 和 MSVC 的行为符合预期。
  • @StoryTeller-UnslanderMonica:[over.match.call.general]/2 表示可以针对重载集的地址进行重载解析,因此模板参数推导可以在此处非常合理地发生。跨度>
  • @DavisHerring - 我们对“合理”的定义不一致。

标签: c++ language-lawyer variadic-templates variadic-functions template-argument-deduction


【解决方案1】:

编辑:问题被编辑后,它现在包含两个正交的子问题,我已经分别处理了。

给定foo&lt;int&gt;(1, 2),是否应该推导出参数包覆盖所有args?

是的。参数包确实出现在 parameter-declaration-list 的末尾,这是判断是否it's non-deduced or not 的标准。这实际上在CWG issue 1569 中得到了澄清。我们可以通过观察所有编译器都同意这很好来说服自己:

template <typename... Args>
constexpr auto foo(Args&& ...args, ...) noexcept {
    return sizeof...(args);
}

static_assert(2 == foo(1, 2), "always true");

只有当我们将foo 更改为foo&lt;int&gt; 时,GCC 才会突然停止推导包。它没有理由这样做,显式向包提供模板参数不应影响它是否有资格进行扣除。

(&amp;T&lt;...&gt;)(...) 形式的调用仍然调用模板参数推导吗?

答案是肯定的,正如公开 CWG 问题 1038 中所讨论的那样:

一个相关的问题涉及一个例子

struct S {
    static void g(int*) {}
    static void g(long) {}
} s;

void foo() {
    (&s.g)(0L);
}

因为地址出现在调用上下文中,而不是在 12.3 [over.over] 第 1 段中提到的上下文之一中,所以 foo 中的调用表达式可能是格式错误的。将此与类似示例进行对比

void g1(int*) {}
void g1(long) {}

void foo1() {
    (&g1)(0L);
} 

这个调用大概是格式良好的,因为 12.2.2.2 [over.match.call] 适用于“一组重载的地址 职能。” (这在决议之前的措辞中更清楚 问题 704:“......在这种情况下,使用 &F 的行为与使用相同 名称 F 本身。”) 目前尚不清楚是否有任何理由 区别对待这两种情况。

正如注释所解释的,在issue 704 之前,我们有这个非常明确的部分:

第四种情况来自&amp;F 形式的后缀表达式,其中F 命名一组重载函数。在里面 在函数调用的上下文中,&amp;F 被视为与名称 F 本身相同。因此,(&amp;F)( expression-listopt) 很简单 F( expression-listopt),在 13.3.1.1.1 中讨论。

这个措辞最终被删除的原因不是它有缺陷,而是整个部分措辞不当。新的措辞仍然明确指出重载决议适用于重载集的地址(这就是我们在这里所拥有的):

如果后缀表达式表示一组重载函数和/或函数模板的地址,则使用上述集合应用重载决议。

【讨论】:

  • 非常感谢,哥伦布。所以,如果我理解正确,正确的输出应该是 2。我说的对吗?
  • 看起来就是这样。
  • 所以它合理地似乎是 GCC 上的一个错误。 ICC 也返回 2。
  • 非常有趣地支持你的论文,如果 foo 不是通过引用调用的,则行为是相同的,即如果我直接使用 return foo&lt;int&gt;(1, 2);。我已经编辑了问题。
  • @GiovanniCerretani 我编辑了答案以涵盖这个问题的两个方面。
猜你喜欢
  • 2013-09-14
  • 2014-02-01
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-24
  • 1970-01-01
  • 2021-04-19
相关资源
最近更新 更多