类模板专业化优先级/歧义

2024-05-17

在尝试依赖可变参数模板实现一些东西时,我偶然发现了一些我无法解释的东西。我将问题归结为以下代码片段:

template <typename ... Args>
struct A {};

template <template <typename...> class Z, typename T>
struct test;

template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
    static void foo() {
        std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
    }
};

template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
    static void foo() {
        std::cout << "I'm variadic!" << std::endl;
    }
};

int main() {
    test<A, A<int>>::foo();
}

在 gcc 下,它会产生错误,因为它在尝试实例化时认为两个专业化是同等专业化的test<A, A<int>>:

main.cpp: In function 'int main()':

main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'

         test<A, A<int>>::foo();

                        ^~

main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]

     struct test<Z, Z<T>> {

            ^~~~~~~~~~~~~

main.cpp:18:12: note:                 template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]

     struct test<Z, Z<T, Args...>> {

然而,clang 认为第一个专业化“更专业”(通过部分排序:参见下一节),因为它编译良好并打印:

我比可变参数规范更专业,呵呵!

A live demo http://coliru.stacked-crooked.com/a/f0c1f71658fac3a3 can be found on Coliru. I also tried using gcc's HEAD version and got the same errors.

我的问题是:由于这两个著名的编译器的行为不同,哪一个是正确的,这段代码是正确的 C++ 吗?


标准解释(C++14 当前草案 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf)

从 C++14 标准草案的第 §14.5.5.1 和 $14.5.5.2 节中,触发部分排序以确定应选择哪个专业化:

(1.2) — 如果找到多个匹配的专业化,则使用偏序规则 (14.5.5.2) 来确定 其中一个专业是否比其他专业更专业。如果没有一个专业 比所有其他匹配的专业化更专业,那么类模板的使用是 模棱两可,程序格式不正确。

现在根据§14.5.5.2,类模板特化通过以下过程转换为函数模板:

对于两个类模板部分特化,第一个比第二个更特化,因为 重写两个函数模板后,第一个函数模板比​​第二个更专业 根据函数模板的排序规则(14.5.6.2):

(1.1) — 第一个函数模板与第一个部分特化具有相同的模板参数,并且具有 单个函数参数,其类型是带有模板参数的类模板特化 第一个部分专业化,以及

(1.2) — 第二个函数模板与第二个部分特化具有相同的模板参数 并且有一个函数参数,其类型是类模板特化与模板 第二部分特化的参数。

因此,我尝试使用上述转换应生成的函数模板重载来重现该问题:

template <typename T>
void foo(T const&) {
    std::cout << "Generic template\n";
}

template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
    std::cout << "Z<T>: most specialized overload for foo\n";
}

template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
    std::cout << "Z<T, Args...>: variadic overload\n";
}

现在尝试像这样使用它:

template <typename ... Args>
struct A {};

int main() {
    A<int> a;
    foo(a);
}

yields a compilation error [ambiguous call] in both clang and gcc: live demo http://coliru.stacked-crooked.com/a/8f4232a317c6f7be. I expected clang would at least have a behavior consistent with the class template case.

然后,这是我对标准的解释(我似乎与@Danh 分享),所以此时我们需要一个语言律师 /questions/tagged/language-lawyer来确认这一点。

Note:我浏览了一点 LLVM 的错误跟踪器,但找不到此问题中在函数模板重载上观察到的行为的票证。


From 临时等级顺序 http://eel.is/c++draft/temp.class.spec#temp.class.order-1:

对于两个类模板部分特化,第一个比第二个更专业如果,给定以下对两个函数模板的重写,根据函数模板的排序规则,第一个函数模板比​​第二个更专业([temp.func.order] http://eel.is/c++draft/temp.func.order):

  • 两个函数模板中的每一个都具有与相应的部分特化相同的模板参数。

  • 每个函数模板都有一个函数参数,其类型是类模板特化,其中模板参数是部分特化的 simple-template-id 的 template-argument-list 中每个模板参数的函数模板中对应的模板参数。

的顺序:

template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
    static void foo() {
        std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
    }
};

template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
    static void foo() {
        std::cout << "I'm variadic!" << std::endl;
    }
};

取决于以下顺序:

template <template <typename...> class Z, typename T>
void bar(test<Z, Z<T>>); // #1
template <template <typename...> class Z, typename T, typename ... Args>
void bar(test<Z, Z<T, Args...>>); // #2

From [temp.func.order] http://eel.is/c++draft/temp.func.order:

部分排序通过依次转换每个模板(请参阅下一段)并使用函数类型执行模板参数推导,选择两个函数模板中哪一个比另一个更专业。推导过程确定其中一个模板是否比另一个更专业。如果是这样,则部分订购过程会选择更专业的模板。

为了生成转换后的模板,对于每个类型、非类型或模板模板参数(包括其模板参数包([temp.variadic]))分别合成一个唯一的类型、值或类模板,并将其替换为每次出现的模板的函数类型中的该参数。

使用转换后的函数模板的函数类型,针对其他模板执行类型推导,如中所述[temp.deduct.partial] http://eel.is/c++draft/temp.deduct.partial.

通过这些段落,对于从任何合成模板转换而来的任何函数Z0并输入T0,可以形成#1,我们可以进行类型推导#2。但功能转变自#2使用虚构模板Z2与任何类型T2以及任何非空集合Args2不能从中推断出#1. #1显然比#2.

clang++ 在这种情况下是正确的。


实际上,this one http://coliru.stacked-crooked.com/a/9d7a233c505962d7 and this one http://coliru.stacked-crooked.com/a/9dbc79376d54c103在 g++ 和 clang 中都无法编译(因为不明确)。似乎两个编译器都很难处理模板模板参数。 (后一个是明确排序的,因为它的顺序与没有函数调用的顺序相同)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

类模板专业化优先级/歧义 的相关文章

  • ROWNUM 的 OracleType 是什么

    我试图参数化所有现有的 sql 但以下代码给了我一个问题 command CommandText String Format SELECT FROM 0 WHERE ROWNUM lt maxRecords command CommandT
  • 属性对象什么时候创建?

    由于属性实际上只是附加到程序集的元数据 这是否意味着属性对象仅根据请求创建 例如当您调用 GetCustomAttributes 时 或者它们是在创建对象时创建的 或者 前两个的组合 在由于 CLR 的属性扫描而创建对象时创建 从 CLR
  • 如何在没有 Control.Invoke() 的情况下从后台线程修改控件属性

    最近 我们遇到了一些旧版 WinForms 应用程序 我们需要更新一些新功能 在专家测试该应用程序时 发现一些旧功能被破坏 无效的跨线程操作 现在 在您认为我是新手之前 我确实有一些 Windows 窗体应用程序的经验 我不是专家 但我认为
  • fgets() 和 Ctrl+D,三次才能结束?

    I don t understand why I need press Ctrl D for three times to send the EOF In addition if I press Enter then it only too
  • 为什么 POSIX 允许在只读模式下超出现有文件结尾 (fseek) 进行搜索

    为什么寻找文件结尾很有用 为什么 POSIX 让我们像示例中那样在以只读方式打开的文件中进行查找 c http en cppreference com w c io fseek http en cppreference com w c io
  • c# Asp.NET MVC 使用FileStreamResult下载excel文件

    我需要构建一个方法 它将接收模型 从中构建excel 构建和接收部分完成没有问题 然后使用内存流导出 让用户下载它 不将其保存在服务器上 我是 ASP NET 和 MVC 的新手 所以我找到了指南并将其构建为教程项目 public File
  • 使用向量的 merge_sort 在少于 9 个输入的情况下效果很好

    不知何故 我使用向量实现了合并排序 问题是 它可以在少于 9 个输入的情况下正常工作 但在有 9 个或更多输入的情况下 它会执行一些我不明白的操作 如下所示 Input 5 4 3 2 1 6 5 4 3 2 1 9 8 7 6 5 4 3
  • 使用安全函数在 C 中将字符串添加到字符串

    我想将文件名复制到字符串并附加 cpt 但我无法使用安全函数 strcat s 来做到这一点 错误 字符串不是空终止的 我确实设置了 0 如何使用安全函数修复此问题 size strlen locatie size nieuw char m
  • Windows 窗体不会在调试模式下显示

    我最近升级到 VS 2012 我有一组在 VS 2010 中编码的 UI 测试 我试图在 VS 2012 中启动它们 我有一个 Windows 窗体 在开始时显示使用 AssemblyInitialize 属性运行测试 我使用此表单允许用户
  • 线程、进程和 Application.Exit()

    我的应用程序由主消息循环 GUI 和线程 Task Factory 组成 在线程中我调用一些第三方应用程序var p new Process 但是当我调用Application Exit 在消息循环中 我可以看到在线程中启动的进程仍在内存中
  • 更改窗口的内容 (WPF)

    我创建了一个简单的 WPF 应用程序 它有两个 Windows 用户在第一个窗口中填写一些信息 然后单击 确定 这会将他们带到第二个窗口 这工作正常 但我试图将两个窗口合并到一个窗口中 这样只是内容发生了变化 我设法找到了这个更改窗口内容时
  • 用 C 实现 Unix shell:检查文件是否可执行

    我正在努力用 C 语言实现 Unix shell 目前正在处理相对路径的问题 特别是在输入命令时 现在 我每次都必须输入可执行文件的完整路径 而我宁愿简单地输入 ls 或 cat 我已经设法获取 PATH 环境变量 我的想法是在 字符处拆分
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • EPPlus Excel 更改单元格颜色

    我正在尝试将给定单元格的颜色设置为另一个单元格的颜色 该单元格已在模板中着色 但worksheet Cells row col Style Fill BackgroundColor似乎没有get财产 是否可以做到这一点 或者我是否必须在互联
  • 窗体最大化时自动缩放子控件

    有没有办法在最大化屏幕或更改分辨率时使 Windows 窗体上的所有内容自动缩放 我发现手动缩放它是正确的 但是当切换分辨率时我每次都必须更改它 this AutoScaleDimensions new System Drawing Siz
  • 如何使用 ReactiveList 以便在添加新项目时更新 UI

    我正在创建一个带有列表的 Xamarin Forms 应用程序 itemSource 是一个reactiveList 但是 向列表添加新项目不会更新 UI 这样做的正确方法是什么 列表定义 listView new ListView var
  • 如何在 C# 中播放在线资源中的 .mp3 文件?

    我的问题与此非常相似question https stackoverflow com questions 7556672 mp3 play from stream on c sharp 我有音乐网址 网址如http site com aud
  • 更改显示的 DPI 缩放大小使 Qt 应用程序的字体大小渲染得更大

    我使用 Qt 创建了一些 GUI 应用程序 我的 GUI 应用程序包含按钮和单选按钮等控件 当我运行应用程序时 按钮内的按钮和字体看起来正常 当我将显示器的 DPI 缩放大小从 100 更改为 150 或 200 时 无论分辨率如何 控件的
  • C++ 成员函数中的“if (!this)”有多糟糕?

    如果我遇到旧代码if this return 在应用程序中 这种风险有多严重 它是一个危险的定时炸弹 需要立即在应用程序范围内进行搜索和销毁工作 还是更像是一种可以悄悄留在原处的代码气味 我不打算writing当然 执行此操作的代码 相反
  • 将 viewbag 从操作控制器传递到部分视图

    我有一个带有部分视图的 mvc 视图 控制器中有一个 ActionResult 方法 它将返回 PartialView 因此 我需要将 ViewBag 数据从 ActionResult 方法传递到 Partial View 这是我的控制器

随机推荐