Clang vs GCC vs MSVC 模板转换运算符 - 哪个编译器是正确的?

2024-01-19

我有带有转换运算符的简单代码,似乎所有编译器都给出了不同的结果,我很好奇哪个编译器(如果有)是正确的? 我也尝试了不同的组合,但以下组合是最有趣的。代码是使用 C++11 标志编译的,但在 C++03 中也可能会观察到相同的行为。

#include <iostream>

struct call_operator {
    template<typename T>
    operator T() {
        std::cout << __FUNCTION__ << std::endl;
        return {};
    }

    template<typename T>
    operator const T&() const {
        std::cout << __FUNCTION__ << std::endl;
        static T t;
        return t;
    }

    template<typename T>
    operator T&() const {
        std::cout << __FUNCTION__ << std::endl;
        static T t;
        return t;
    }
};

int main() {
    (void)static_cast<int>(call_operator());
    (void)static_cast<const int&>(call_operator());
    (void)static_cast<int&>(call_operator());
}

铿锵-3.6:

operator int
operator const int &
operator int &

g++-4.9:

operator T
operator const T&
operator T&

MSVC 2014 CTP:

call_operator.cpp(17): error C2440: 'static_cast': cannot convert from 'call_operator' to ' const int &'

删除后:

template<typename T>
operator T();

msvc编译:

call_operator::operator const int &
call_operator::operator const int &
call_operator::operator int &

此外,删除 const 后

template<typename T>
operator const T&();

铿锵-3.6:

call_operator.cpp:26:9: error: ambiguous conversion for static_cast from 'call_operator' to 'int' (void)static_cast<int>(call_operator());

g++-4.9:

operator T
operator const T&
operator T&

MSVC 2014 CTP:

call_operator.cpp(16): error C2440: 'static_cast': cannot convert from 'call_operator' to 'int'

In short: Clang 是正确的(尽管在一种情况下,出于错误的原因)。 GCC 在第二种情况下是错误的。 MSVC 在第一种情况下是错误的。

让我们从static_cast(§5.2.9 [expr.static.cast]/p4,所有引用均来自 N3936):

一种表达e可以显式转换为类型T用一个static_cast形式的static_cast<T>(e)如果声明T t(e);对于某些发明的临时变量来说是格式良好的t(8.5)。 这种显式转换的效果与执行 声明和初始化,然后使用临时 变量作为转换的结果。表达方式e用来 当且仅当初始化将其用作左值时,才将其用作左值。

据此,三static_cast这里实际上是三个初始化:

int t1(call_operator{});
const int & t2(call_operator{});
int & t3(call_operator{});

请注意,我们重写了call_operator() as call_operator{}仅供说明之用,如int t1(call_operator());是最令人烦恼的解析。这两种形式的初始化之间存在细微的语义差异,但这种差异对于本次讨论来说并不重要。

int t1(call_operator{});

§8.5 [dcl.init]/p17 中列出了此初始化的适用规则:

如果源类型是(可能是 cv 限定的)类类型,则转换 功能被考虑。适用的转换函数是 枚举(13.3.1.5),并通过重载选择最好的一个 决议(13.3)。如此选择的用户定义的转换称为 将初始化表达式转换为对象 已初始化。如果转换无法完成或不明确,则 初始化格式不正确。

我们继续进行第 13.3.1.5 节 [over.match.conv],其中表示:

假如说 ”cv1 T” 是正在初始化的对象的类型, 和 ”cv S” 是初始化表达式的类型,其中SA 类类型,候选函数选择如下:

  • 的转换函数为S及其基类被考虑。那些没有隐藏在其中的非显式转换函数S和产量类型T或可以转换为 type 的类型T通过一个 标准转换序列(13.3.3.1.1)是候选函数。为了 直接初始化,那些显式转换函数 不隐藏在里面S和产量类型T或者一个类型可以是 转换为类型T也有资格转换(4.4) 候选函数。返回 cv 限定的转换函数 类型被认为产生该类型的 cv-unqualified 版本 用于选择候选函数的过程。转换 返回“对 cv2 的引用”的函数X” 返回左值或 xvalues,取决于引用类型,类型为“cv2X”并且是 因此认为产生X对于这个选择的过程 候选函数。

2 参数列表有一个参数,它是初始化器 表达。 [Note:这个论点将与 转换函数的隐式对象参数。 —end note ]

模板实参推导后的候选集为:

operator T() - with T = int
operator const T& () const - with T = int
operator T&() const - with T = int

参数列表由单个表达式组成call_operator{},这是非常量。因此,它可以更好地转换为非常量隐式对象参数operator T()比其他两个。因此,operator T()是最佳匹配,由重载决策选择。

const int & t2(call_operator{});

此初始化受 §8.5.3 [dcl.init.ref]/p5 管辖:

对类型“cv1”的引用T1” 由类型表达式初始化 “简历2T2“ 如下:

  • 如果引用是左值引用并且初始化表达式

    • 是左值(但不是位字段),并且“cv1 T1”与“cv2”引用兼容T2,” or
    • 有一个类类型(即T2是一个类类型),其中T1与参考无关T2,并且可以转换为类型的左值 “CV3T3,”其中“cv1T1” 与“cv3 引用兼容T3” (此转换是通过枚举适用的转换来选择的 函数(13.3.1.6)并通过重载选择最好的函数 决议(13.3))。

然后引用绑定到初始化表达式左值 第一种情况和转换的左值结果 第二种情况(或者在任何一种情况下,到适当的基类 对象的子对象)。

请注意,此步骤仅考虑返回左值引用的转换函数。

Clang appears to deduce the candidate set as*:

operator const T& () const - with T = int
operator T&() const - with T = int

很明显,这两个函数都依赖于隐式对象参数,因为它们都是const。此外,由于两者都是直接引用绑定,根据 §13.3.3.1.4 [ics.ref]/p1,需要从任一函数的返回类型转换为const int &是身份转换。 (Not资格调整 - 指第 4.4 节 [conv.qual] 中描述的转换,仅适用于指针。)

However, it appears that the deduction performed by Clang for operator T&() in this case is incorrect. §14.8.2.3 [temp.deduct.conv]/p5-6:

5 一般来说,推导过程试图找到模板实参 的值将使得推导A相同A。然而,有 有两种情况允许存在差异:

  • 如果原来的A是一个引用类型,A可以比推导的 A 更符合 cv 条件(即,由 参考)
  • 推导出来的A可以是另一个指针或指向可转换为的成员类型的指针A通过资格转换。

6 仅当类型推导能够实现时才考虑这些替代方案 否则失败。如果它们产生不止一种可能的推论A, 这 类型推导失败。

由于类型推导可以通过推导成功T as const int for operator T&()对于推导类型和目标类型之间的精确匹配,不应考虑替代方案,T应该被推论为const int,而候选集实际上是

operator const T& () const - with T = int
operator T&() const - with T = const int

Once again, both standard conversion sequences from the result are identity conversions. GCC (and EDG, thanks to @Jonathan Wakely for testing) correctly deduces T in operator T&() to be const int in this case*.

然而,无论推论的正确性如何,这里的决胜局都是一样的。因为,根据函数模板的偏序规则,operator const T& ()比更专业operator T&()(由于§14.8.2.4 [temp.deduct.partial]/p9中的特殊规则),前者在§13.3.3 [over.match.best]/p1中通过决胜局获胜,第二个列表,最后一个要点:

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

因此,在这种情况下,Clang 得到了正确的结果,但原因(部分)错误。 GCC 出于正确的原因得到了正确的结果。

int & t3(call_operator{});

这里没有战斗。operator const T&();根本不可能用于初始化int &。只有一个可行的功能,operator T&() with T = int,所以它是最好的可行函数。

What if operator const T&(); isn't const?

这里唯一有趣的情况是初始化int t1(call_operator{});。两个强有力的竞争者是:

operator T() - with T = int
operator const T& () - with T = int

请注意,有关标准转换序列排名的规则 - §13.3.3 [over.match.best]/p1,第二个列表,第二个要点:

上下文是通过用户定义的转换进行的初始化(参见 8.5, 13.3.1.5 和 13.3.1.6) 以及来自返回类型的标准转换序列F1到目标类型(即 正在初始化的实体)是比 从返回类型的标准转换序列F2到 目的地类型。

和 §13.3.3.2 [over.ics.rank]/p2:

标准转换顺序S1是一个比以下更好的转换序列 标准转换顺序S2 if

  • S1是一个真子序列S2(比较 13.3.3.1.1 定义的规范形式的转换序列,不包括任何 左值变换;考虑身份转换序列 是任何非恒等转换序列的子序列)

无法区分这两者,因为获得一个所需的转换int from a const int & is an 左值到右值转换,这是左值转换。排除左值变换后,从结果到目标类型的标准转换序列是相同的; §13.3.3.2 [over.ics.rank] 中的任何其他规则也不适用。

因此,可能区分这两个功能的唯一规则仍然是“更专业”的规则。那么问题是是否其中之一operator T() and operator const T&()比另一个更专业。答案是不。详细的部分排序规则相当复杂,但在 §14.5.6.2 [temp.func.order]/p2 的示例中很容易找到类似的情况,该示例将调用标记为g(x)由于含糊不清:

template<class T> void g(T);
template<class T> void g(T&);

A quick perusal of the procedure specified in §14.8.2.4 [temp.deduct.partial] confirms that given one template taking a const T& and the other taking a T by value, neither is more specialized than the other**. Thus, in this case, there is no unique best viable function, the conversion is ambiguous, and the code is ill-formed.


* The type deduced by Clang and GCC for the operator T&() case is determined by running the code with operator const T&() removed.

** Briefly, during the deduction for partial ordering, before any comparison is done, reference types are replaced with the types referred to, and then top-level cv-qualifiers are stripped, so both const T& and T yield the same signature. However, §14.8.2.4 [temp.deduct.partial]/p9 contains a special rule for when both types at issue were reference types, which makes operator const T&() more specialized than operator T&(); that rule doesn't apply when one of the types is not a reference type.

GCC appears to not consider operator const T&() a viable conversion for this case, but does consider operator T&() a viable conversion.

This appears to be Clang bug 20783 http://llvm.org/bugs/show_bug.cgi?id=20783.

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

Clang vs GCC vs MSVC 模板转换运算符 - 哪个编译器是正确的? 的相关文章

随机推荐

  • 如何使用退出代码退出程序:C#

    如何在 C 中使用退出代码退出程序 在java中它会是System exit int code http download oracle com javase 1 4 2 docs api java lang System html exi
  • Google 天气 API 403 错误 [重复]

    这个问题在这里已经有答案了 我决定从 Google 的天气 API 中提取信息 我在下面使用的代码运行良好 XmlDocument widge new XmlDocument widge Load https www google com
  • 乘以 100 后的值错误[重复]

    这个问题在这里已经有答案了 当该值 1212 3456789123 乘以 100 时 jQuery 显示 12 123456789123003 而不是 12 123456789123 Code p class price 12 123456
  • 使用多个不同的 group_by 变量 (dplyr) 来总结数据帧

    我有一个数据框 my data 其中包含 6 列 group1 members group2 members group3 members price price 2 price 3 1 1 1 800 877 334 1 2 1 850
  • 角度模块中的多个配置块

    我需要在加载之前解决应用程序中的一些依赖项 为我的服务获取数据等 我想将它们分开 以便我为主应用程序拥有一个配置块 然后为应用程序的其他部分拥有一个或多个配置块 最终 我希望它能够解决主应用程序的依赖关系 加载与之关联的组件 然后解决其余部
  • 使用动态内容调整 fancybox 的大小

    我正在使用 fancybox 当盒子加载时 我需要将一些项目插入盒子内的无序列表 ul 中 问题是 当我插入项目时 内容比框本身大 因此我需要在内容更新后调整框的大小 我无法让它与 fancybox resize 一起使用 添加新内容后如何
  • 在 virtualenv 中编译 mod_wsgi 时出现问题

    我正在尝试在 CentOS 服务器上编译 mod wsgi 版本 3 3 Python 2 6 但在virtualenv 没有成功 我收到错误 usr bin ld home python26 lib libpython2 6 a node
  • Java 图像未显示?

    尝试显示我的徽标时遇到问题 图片保存在与main java同一个文件夹中 ImageIcon im new ImageIcon banner png JLabel bam new JLabel im grid add bam 我的语法有问题
  • 选择所有列,但用 Google BigQuery 中的表达式替换一些列?

    如同在 Google BigQuery 中选择除某些列之外的所有列 https stackoverflow com questions 34056485 select all columns except some in google bi
  • PHPUnit 测试速度慢

    我正在运行 PHPUnit 来使用 CIUnit 两者之间的第三方接口 测试 CodeIgniter 应用程序 许多测试从空的 MySQL 数据库中选择数据 该数据库在 setUp 中填充了 5 10 条记录 在 Windows 和 Web
  • 计算屏幕上显示的列表项目并且不溢出

    当溢出设置为隐藏时 如何统计屏幕上显示的所有列表项 使用下面的代码仍然会计算所有项目 甚至是溢出的项目 var count myList ul li visible length Fiddle http jsfiddle net kPAwX
  • Java ProcessBuilder 显示启动的 java 应用程序的控制台?

    我有一个 JAVA 应用程序 它启动 使用 ProcessBuilder 另一个 JAVA 应用程序 如下所示 String val something ProcessBuilder processBuilder new ProcessBu
  • CoordinatorLayout 内 ViewPager 中的非滚动片段

    我在活动的 CoordinatorLayout 来自最新版本的设计库 中使用 ViewPager 此 ViewPager 的某些片段具有 RecyclerView 或 NestedScrollView 等布局 但有些片段由于内容较小而无法滚
  • 休眠注释。 @Where 与 @WhereJoinTable

    遵循java文档 Where 用于添加到集合的元素 Entity 或目标实体的Where 子句 该子句是用 SQL 编写的 这里的一个常见用例是软删除 WhereJoinTable 添加到集合连接表的Where 子句 该子句是用 SQL 编
  • MacOS:无法运行 MySQL Workbench

    我一直在尝试在我的上运行MySQL工作台macOS Catalina 10 15 2很长一段时间了 我一直在 CLI 中使用 MySQL 但最近当我尝试切换到工作台时 出现了一些问题启动工作台应用程序时出现问题 MySQL服务器从 CLI
  • 标记扩展“StaticResourceExtension”要求在 ProvideValue 的 IServiceProvider 中实现“IXamlSchemaContextProvider”

    我已经使用了几年的资源扩展现在在新的 Net 4 项目的设计时停止工作 并出现以下错误 标记扩展 StaticResourceExtension 要求在 ProvideValue 的 IServiceProvider 中实现 IXamlSc
  • UITableViewCell 内的 UIView 动画

    我正在开发一个 iOS 应用程序 该应用程序在一个视图中具有UITableView有两个定制UITableViewCell 所有这些都使用主故事板 一切都很好 但我需要实现的是一个单一的淡入 淡出动画UIView from just one
  • 让VS2008“树形缩进”部分类(如代码隐藏文件)

    我创建了一个多平台项目 除了一些小事情之外 一切都运行良好 当我添加特定的平台文件时 例如 ServiceImpl cs ServiceImpl Desktop cs 它并没有像本文中那样以树形方式很好地显示 多目标 http www mo
  • 导入的表没有显示在 phpmyadmin [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我导出了生
  • Clang vs GCC vs MSVC 模板转换运算符 - 哪个编译器是正确的?

    我有带有转换运算符的简单代码 似乎所有编译器都给出了不同的结果 我很好奇哪个编译器 如果有 是正确的 我也尝试了不同的组合 但以下组合是最有趣的 代码是使用 C 11 标志编译的 但在 C 03 中也可能会观察到相同的行为 include