【问题标题】:gcc vs. clang, msvc and icc: Is this function call ambiguous?gcc vs. clang、msvc 和 icc:这个函数调用是否有歧义?
【发布时间】:2016-09-02 06:57:12
【问题描述】:

我能接触到的所有编译器都同意这很好:

template <typename Check, typename... T>
auto foo(Check, T...) -> void;

template <typename... T>
auto foo(int, T...) -> void;

int main()
{
  foo(7, "");
}

但是,根据 gcc,以下代码(带有无法从函数参数推导出的前导模板参数)是模棱两可的:

template <typename X, typename Check, typename... T>
auto bar(Check, T...) -> void;

template <typename X, typename... T>
auto bar(int, T...) -> void;

int main()
{
  bar<void>(7, ""); // ambiguous according to gcc
  bar<void>(7);     // just fine
}

另一方面,clang、msvc 和 icc 对此非常满意。

哪个编译器是正确的?

参考标准的各个部分首选。

【问题讨论】:

  • 这个问题的核心当然是两个重载在实参类型和形参之间都有一个精确的匹配,所以在模板实参的重载中还有一个规则。
  • Gutfeeling 说这不是模棱两可的。附加参数不应有所不同,因为“出于部分排序目的,模板参数可能会保留没有值,前提是它未用于用于部分排序的类型中。”,并且有一个与您的代码匹配的规则示例取模参数包。
  • 如果 gcc 接受了仅包含 7 的附加示例,那么我的引用不再解释这种行为差异。
  • @JohannesSchaub-litb 这似乎是正确的。我把它放在一个答案中。

标签: c++ c++11 language-lawyer partial-ordering


【解决方案1】:

这是core issue 200

模板函数偏序的描述 在 14.5.6.2 [temp.func.order] 第 3-5 段中确定没有使 对非推导模板参数的任何规定。例如, 以下代码中的函数调用是模棱两可的,即使一个 模板“显然”比另一个更专业:

template <class T> T f(int);
template <class T, class U> T f(U);
void g() {
    f<int>(1);
}

原因是两个函数参数列表都不允许推导模板参数T;两个扣除都失败了,所以两者都没有 模板被认为比另一个更专业,并且 函数调用不明确。

core issue 214的分辨率,这个被降到了,引入[temp.deduct.partial]/11

在大多数情况下,所有模板参数都必须有值才能成功进行推导,但出于偏序目的,模板参数可能会保持没有值,前提是它未用于偏序的类型中。

显然,一旦包开始发挥作用,GCC 对该措辞的实施就会出现问题。

【讨论】:

    【解决方案2】:

    恕我直言,我认为 GCC 是错误的,而 CLANG 在这里是正确的。我将尝试在下面证明我的主张:

    根据标准§14.8.3/p1 过载分辨率 [temp.over](Emphasis Mine

    函数模板可以通过(非模板)重载 其名称的功能或相同的(其他)功能模板 姓名。当写入对该名称的调用时(显式或隐式 使用运算符符号),模板参数推导(14.8.2) 并检查任何显式模板参数(14.3) 为每个函数模板查找模板参数值(如果 any) 可以与该函数模板一起使用以实例化一个 可以通过调用调用的函数模板特化 论据。对于每个函数模板,如果参数推导和 检查成功,模板参数(推导和/或显式) 用于综合单个函数模板的声明 添加到候选函数集的特化 用于重载决议。如果,对于给定的函数模板, 参数推导失败或合成函数模板 专业化将是不正确的,没有这样的功能被添加到 该模板的候选函数集。全套 候选函数包括所有综合声明和所有 的同名非模板重载函数。这 综合声明被视为与任何其他函数一样 重载决议的其余部分,除非明确指出 13.3.3.144

    [示例:

    template<class T> T max(T a, T b) { return a>b?a:b; }
    void f(int a, int b, char c, char d) {
    int m1 = max(a,b); // max(int a, int b)
    char m2 = max(c,d); // max(char a, char b)
    int m3 = max(a,c); // error: cannot generate max(int,char)
    }
    

    144) 函数模板特化的参数包含 没有模板参数类型。推导上允许的转换集 论据是有限的,因为论据推演过程产生 具有与调用匹配的参数的函数模板 论点完全或不同,仅在可以由 允许有限的转换。非推导论据允许完整 转换范围。另请注意,13.3.3 指定一个 非模板函数将优先于模板 如果两个函数在其他方面同样好,则进行专业化 重载匹配的候选者。

    从上面我们得到显式模板参数将被检查,如果检查成功,则将用于合成一个特化,该特化将添加到候选函数中以进行重载解析。因此,您明确指定 X 的事实与该过程无关。

    同样来自 C++ 标准§13.3.3/p1.7 最佳可行函数 [over.match.best]

    F1F2 是函数模板的特化,函数 F1 的模板比 F2 的模板更专业 根据 14.5.6.2 中描述的部分排序规则。

    现在从 §14.5.6.2/p3 函数模板的部分排序 [temp.func.order] 我们了解到,在部分排序中,参数包也在起作用,所以这里也没有问题。

    现在:

    template <typename X, typename... T>
    auto bar(int, T...) -> void;
    

    比以下更专业:

    template <typename X, typename Check, typename... T>
    auto bar(Check, T...) -> void;
    

    因此调用:

    bar<void>(7, "");
    

    没有歧义。

    根据上述情况,我认为这是一个 GCC 错误。

    【讨论】:

    • 现在已经多次阅读 14.5.6.2 [temp.func.order],我仍然无法识别出 template &lt;typename X, typename... T&gt; auto bar(int, T...) -&gt; void;template &lt;typename X, typename Check, typename... T&gt; auto bar(Check, T...) -&gt; void; 更专业的部分。你能指出正确的句子吗?
    猜你喜欢
    • 1970-01-01
    • 2012-12-04
    • 1970-01-01
    • 2016-09-12
    • 1970-01-01
    • 2021-08-12
    • 2019-06-08
    • 2022-09-28
    • 1970-01-01
    相关资源
    最近更新 更多