gpt4 book ai didi

c++ - 可变参数函数指针参数的模板参数推导 - 处理不明确的情况

转载 作者:行者123 更新时间:2023-12-02 13:24:24 26 4
gpt4 key购买 nike

考虑以下代码:

#include <iostream>

void f(int) { }

void f(int, short) { }

template<typename... Ts> void g(void (*)(Ts...))
{
std::cout << sizeof...(Ts) << '\n';
}

template<typename T, typename... Ts> void h(void (*)(T, Ts...))
{
std::cout << sizeof...(Ts) << '\n';
}

int main()
{
g(f); // #1
g<int>(f); // #2
h(f); // #3
h<int>(f); // #4
}

目的是尝试 main() 正文中的每一行分别地。我的期望是所有四个调用都是不明确的,并且会导致编译器错误。

我测试了以下代码:
  • Clang 3.6.0 和 GCC 4.9.2,都使用 -Wall -Wextra -pedantic -std=c++14 ( -std=c++1y 对于 GCC) - 在所有这些情况下的行为相同,除了错误消息的措辞略有不同;
  • Visual C++ 2013 Update 4 和 Visual C++ 2015 CTP6 - 同样,同样的行为,所以我将它们称为“MSVC”。

  • Clang 和 GCC:
  • #1 : 编译器错误,带有混淆信息,基本上是 no overload of 'f' matching 'void (*)()' .什么?无参数声明从何而来?
  • #3 :编译器错误,还有一条令人困惑的消息:couldn't infer template argument 'T' .在那里可能会失败的所有事情中,推导出 T 的论点将是我期望的最后一个...
  • #2#4 : 编译时没有错误和警告,并选择第一个重载。

  • 对于所有四种情况,如果我们消除重载之一(任何一种),代码编译良好并选择剩余的函数。这看起来像 Clang 和 GCC 中的不一致:毕竟,如果分别对两个重载进行推导成功,那么在 #2 情况下如何选择一个而不是另一个。和 #4 ?他们俩不是绝配吗?

    现在,MSVC:
  • #1 , #3#4 : 编译器错误,有一条好消息:cannot deduce template argument as function argument is ambiguous .现在这就是我要说的!但是,等等...
  • #2 : 编译时没有错误和警告,并选择第一个重载。分别尝试两个重载,只有第一个匹配。第二个产生错误:cannot convert argument 1 from 'void (*)(int,short)' to 'void (*)(int)' .没那么好了。

  • 澄清我正在寻找的案例 #2 ,这是标准(N4296,C++14 final 后的初稿)在[14.8.1p9]中所说的:

    Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments.



    看起来这部分在 MSVC 中不太适用,使其选择 #2 的第一个重载.

    到目前为止,看起来 MSVC 虽然不太正确,但至少是相对一致的。 Clang 和 GCC 怎么了?根据每个案例的标准,正确的行为是什么?

    最佳答案

    据我所知,根据标准,Clang 和 GCC 在所有四种情况下都是正确的,尽管它们的行为可能看起来违反直觉,尤其是在 #2 的情况下。和 #4 .

    代码示例中函数调用的分析主要有两个步骤。第一个是模板参数推导和替换。完成后,它会生成一个特化声明( gh ),其中所有模板参数都已替换为实际类型。

    然后,第二步尝试匹配 f的重载针对在上一步中构造的实际函数指针参数。根据[13.4]-重载函数地址中的规则选择最佳匹配;在我们的例子中,这非常简单,因为在重载中没有模板,所以我们要么有一个完美的匹配,要么根本没有。

    理解这里发生了什么的关键是第一步中的歧义并不一定意味着整个过程失败。

    下面的引用来自 N4296,但内容自 C++11 以来没有改变。

    [14.8.2.1p6] 描述了当函数参数是指向函数的指针时模板参数推导的过程(重点是我的):

    When P is a function type, pointer to function type, or pointer to member function type:
    — If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.
    — If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context.



    为了完整起见,[14.8.2.5p5] 澄清了即使没有匹配项,同样的规则也适用:

    The non-deduced contexts are: [...]
    — A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions (13.4), and one or more of the following apply:
    — more than one function matches the function parameter type (resulting in an ambiguous deduction), or
    — no function matches the function parameter type, or
    — the set of functions supplied as an argument contains one or more function templates.



    因此,在这些情况下不会因为模棱两可而导致硬错误。相反,在我们所有的情况下,所有模板参数都在非推导的上下文中。这与 [14.8.1p3] 结合:

    [...] A trailing template parameter pack (14.5.3) not otherwise deduced will be deduced to an empty sequence of template arguments. [...]



    虽然这里使用“推导”一词令人困惑,但我认为这意味着如果没有可以从任何来源推导出元素并且没有明确指定的模板参数,模板参数包被设置为空序列.

    现在,来自 Clang 和 GCC 的错误消息开始变得有意义(只有在您理解错误发生的原因后才有意义的错误消息并不完全是有用错误消息的定义,但我想它总比没有好):
  • #1 : 自 Ts为空序列,g的参数的专业确实是void (*)()在这种情况下。然后编译器尝试将重载之一与目标类型匹配并失败。
  • #3 :T只出现在非推导的上下文中并且没有明确指定(并且它不是参数包,所以它不能是“空的”),所以不能为 h 构造特化声明,因此消息。

  • 对于编译的情况:
  • #2 :Ts无法推导出,但明确指定了一个模板参数,所以 Tsint , 制作 g的特化参数void (*)(int) .然后将重载与此目标类型进行匹配,并选择第一个。
  • #4 :T明确指定为 intTs是空序列,所以 h的特化参数是void (*)(int) ,同上。

  • 当我们消除其中一个重载时,我们消除了模板参数推导过程中的歧义,因此模板参数不再处于非推导上下文中,允许根据剩余的重载推导它们。

    快速验证是添加第三个重载
    void f() { }

    允许案例 #1编译,这与上述所有内容一致。

    我想事情是以这种方式指定的,以允许从其他来源获取函数指针参数中涉及的模板参数,例如其他函数参数或显式指定的模板参数,即使模板参数推导无法基于指向函数参数本身的指针。这允许在更多情况下构造函数模板特化声明。由于重载随后与合成特化的参数匹配,这意味着即使模板参数推导不明确,我们也可以选择重载。如果这是您所追求的,则非常有用,在其他一些情况下非常令人困惑 - 真的没有什么不寻常的。

    有趣的是,MSVC 的错误信息虽然表面上很好而且很有帮助,但实际上误导了 #1。 , 对 #3 有点帮助但不是很有帮助,对于 #4 不正确.此外,它对 #2 的行为正如问题中所解释的那样,是其实现中的一个单独问题的副作用;如果不是这样,它可能会为 #2 发出相同的错误消息。以及。

    这并不是说我喜欢 Clang 和 GCC 的 #1 的错误消息和 #3 ;我认为他们至少应该包括一个关于非推断上下文及其发生原因的注释。

    关于c++ - 可变参数函数指针参数的模板参数推导 - 处理不明确的情况,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29417534/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com