将可变参数模板与 C 样式可变参数函数混合时的模板参数推导

Template argument deduction when mixing variadic template with C-style variadic function

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<int> 和 returns 1,而 clang 13 和 MSVC 2019 都推导出 foo<int, int>bar returns 2.

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

哪个输出是正确的?

编辑:

如果我直接使用 return foo<int>(1, 2);,即使用

,错误行为仍然存在
constexpr auto bar() noexcept {
    return foo<int>(1, 2);
}

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

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

给定foo<int>(1, 2),是否应该推导参数包以覆盖所有参数?

是的。 parameter pack确实出现在parameter-declaration-list的末尾,这是判断是否it's non-deduced or not. This was actually clarified in 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<int>时,GCC突然停止推导包。它没有理由这样做,明确地向包提供模板参数不应该影响它是否符合扣除条件。

(&T<...>)(...) 形式的调用是否仍然调用模板参数推导?

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

A related question concerns an example like

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

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

Because the address occurs in a call context and not in one of the contexts mentioned in 12.3 [over.over] paragraph 1, the call expression in foo is presumably ill-formed. Contrast this with the similar example

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

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

This call presumably is well-formed because 12.2.2.2 [over.match.call] applies to “the address of a set of overloaded functions.” (This was clearer in the wording prior to the resolution of issue 704: “...in this context using &F behaves the same as using the name F by itself.”) It's not clear that there's any reason to treat these two cases differently.

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

The fourth case arises from a postfix-expression of the form &F, where F names a set of overloaded functions. In the context of a function call, &F is treated the same as the name F by itself. Thus, (&F)( expression-listopt ) is simply F( expression-listopt ), which is discussed in 13.3.1.1.1.

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

If the postfix-expression denotes the address of a set of overloaded functions and/or function templates, overload resolution is applied using that set as described above.