当前位置: 首页 > 知识库问答 >
问题:

在 switch 语句条件下同时具有模板和非模板转换运算符的类

燕俊明
2023-03-14

问题最初出现在这个问题中。考虑以下代码

class Var
{
public:

    operator int () const
    { return 0; }

    template <typename T>
    operator T () const
    { return T(); }

};

int main()
{
    Var v;
    switch (v)
    { }
}

没有<code>运算符int()常量{return 0;},g和clang都拒绝该代码。

但是,上面的代码,带有运算符 int(),被 clang 接受,但被 g 拒绝,并出现以下错误:

main.cpp:17:14: error: default type conversion can't deduce template argument for 'template<class T> Var::operator T() const'
     switch (v)
              ^

哪个编译器是正确的?

共有3个答案

周玺
2023-03-14

我相信gcc是正确的,但是标准有缺陷。

GCC 是正确的,因为该标准要求对 Switch 中使用的类型使用单个非显式转换运算符到整型或枚举类型。

标准是错误的,因为检测这种情况涉及解决停止问题。

运算符 T 可以附加任意复杂度的 SFINAE 子句。根据标准,编译器必须确定是否存在 T,使得 T枚举

template<class...Ts>
struct evil {
  enum { bob = 3+sizeof...(Ts) };
};

struct test {
  operator int() const { return -1; };
  template<class T, typename std::enable_if<T::bob==2>::type* unused=nullptr>
  operator T() const { return T::bob; }
};
int main() {
  switch( test{} ) {
    case -1: std::cout << "int\n"; break;
    case 2: std::cout << "bob\n"; break;
    default: std::cout << "unexpected\n"; break;
  }
}

上面的代码演示了一种情况,其中我们有无限多的<code>枚举</code>隐式可用。我们有一个<code>运算符T</code>,当且仅当<code>T::bob==2</code>时,它将转换为<code>T类型。现在,我们的程序中没有这样的enums(即使我们删除了3,仍然没有,因为它不是 ——很容易纠正)。

因此test只能转换为int,因此开关语句应该编译。gcc未能通过此测试,并声称template操作符T使其变得模棱两可(当然没有告诉我们T是什么)。

将< code>enum type替换为< code>enum class type,并删除< code>3 会使< code>switch语句在标准下成为非法语句。但是对于编译器来说,它基本上必须在程序中实例化所有可能的模板,寻找一个秘密的< code>enum,它具有所讨论的属性。只要做一点工作,我就可以强迫编译器解决NP完全问题(或者,排除编译器限制,停止问题),以确定一个程序是否应该编译。

我不知道正确的措辞应该是什么。但书面的措辞肯定不合理。

宇文德明
2023-03-14

6 . 4 . 2/2 < code > switch 语句(强调我的)

该条件应为整数类型、枚举类型或存在单个非显式整数或枚举类型转换函数的类类型(12.3)。如果条件是类类型的,则通过调用转换函数来转换条件,转换的结果将用于替换本节剩余部分的原始条件。

所以我的解释是g在这里是正确的。

邓兴为
2023-03-14

我相信这里的< code>clang是正确的。

我们可以从C标准草案第6.4.2节switch语句中看到,这涉及到一个上下文隐式转换。第2段说(*强调我的未来):

条件应为整型、枚举型或类型。如果是类类型,则条件在上下文中隐式转换为整型或枚举类型(子句 4)。

我们可以看到我们需要使用的部分是 4 标准转换,第 5 段涵盖了这些情况,它说:

某些语言结构需要转换为具有适合该结构的指定类型集之一的值。在这样的上下文中出现的类类型E的表达式e被称为上下文隐式转换为指定类型T,并且当且仅当e可以隐式转换为类型T时才是格式良好的,该类型T确定如下:搜索返回类型为cv T或引用cv T的转换函数,以便上下文允许T。应该有一个这样的T。

这没有参考第8.5节,该节允许通过特别参考第13.313.3节来解决过载问题,但不允许我们不能使用的过载问题:

template <typename T>
operator T () const

因此没有歧义。

请注意,这与第4段不同,第4段涵盖了if、while等上下文中的bool转换...说(强调我的):

某些语言构造要求将表达式转换为布尔值。在这样的上下文中出现的表达式 e 被称为在上下文中转换为 bool,并且当且仅当声明 bool t(e) 时,格式良好;对于某些发明的临时变量 T (8.5) 来说,格式良好。

它特别允许过载解决,并直接引用涵盖这一点的第 13.3 节。这是有道理的,因为我们有一个特定的目标类型 bool 可以转换为我们在开关情况下没有的。

为什么

我们可以通过查看N3323:调整某些C上下文转换的建议v3来解决这个问题。它涵盖了这个问题。很难引用整篇论文,所以我将尝试引用足够多的上下文。它说:

C 表达式出现的上下文通常会影响表达式的计算方式,因此可能会对表达式施加要求以确保此类计算是可能的。[...]

在四种情况下,FDIS(N3290)使用不同的语言来指定类似的上下文相关转换。在这四个上下文中,当操作数为类类型时,该类型必须具有“单个非显式转换函数”,以转换为合适的(上下文特定的)类型。[...]

并包括:

[stmt.switch]/2:"条件应为整数类型、枚举类型或类类型,对于这些类型,存在一个到整数或枚举类型的非显式转换函数(12.3)。"

并说:

在导言中引用的四个上下文中,主要问题似乎都在于它们共同的有用但非常严格的要求,即将一个类限制为只有一个转换运算符[…]

另一个问题是当前措辞中限定词“single”的范围。类中必须只有一个转换函数,或者只要一个函数适合上下文就可以有几个转换函数?

目前的措辞在这一点上似乎不明确。也不清楚生成对适当类型的引用的转换运算符是否是适当的转换运算符。(关于这一点的一个问题已于2011年2月21日张贴到核心反射器上,但在撰写本文时尚未得到答复。)当前的编译器实践似乎允许这样的操作符,但当前的语言似乎不允许。

并建议:

为了解决所有这些问题,我们建议改用经过验证的方法,该方法以 [conv]/3 中定义的术语上下文转换为 bool 为代表。因此,我们建议对 [conv]/3 进行适度的添加,以定义到其他指定类型的上下文转换,然后诉诸这个新定义。

新的语言如下:

某些其他语言结构需要类似的转换,但转换为具有适合该结构的指定类型集之一的值。在这样的上下文中出现的类类型E的表达式e被称为在上下文中隐式转换为指定类型T,并且当且仅当e可以隐式转换为类型T时才是格式良好的,该类型T确定如下:搜索返回类型为cv T或引用cv T的转换函数,以便上下文允许T。应该有一个这样的T。

注N3486: C编辑报告,2012年10月向我们展示了< code>N3323何时被纳入标准草案。

[更新]

提交了一份gcc错误报告。

 类似资料:
  • 在下面的代码中,是一个模板类,取决于非类型参数。为和定义了friend。还依赖于另一个bool模板参数。 在Coliru上看现场直播。 现在,我想给出的模板参数的默认值,例如,以便以下语句 相当于 如果我在 同样,它不编译给出错误 main.cpp:27:15:错误:重新声明friend'template std::ostream&operator<<(std::ostream&,const a&

  • 我在运算符过载时遇到问题 主要是我有 其中<code>Integer</code>只是<code>int</code>的包装,带有我需要的一些特殊功能。 然而,当我编译上面的代码时,我得到了错误C2679,它表示<code>binary' 我还试图删除友元声明中的参数,因此代码变成了: 但这会产生另一个错误:C2785:

  • 我希望输出日志消息的方式与使用bash“logger”命令的方式完全相同,但在Java中使用Log4j: 在尝试了许多不同的模式后,我无法再现相同的输出。知道怎么写这个图案吗? 谢谢你,

  • 目标 知道何时和如何使用关系运算符、条件运算符和控制语句 了解变量范围的概念和它的基本规则 熟悉三元运算符 关系和条件运算符 Java 语言提供了运算符和控制语句,可在代码中使用它们来制定决策。代码中的决策通常从一个布尔表达式(一个计算为 true 或 false 的表达式)开始。这些表达式使用关系运算符(用于将一个操作数与另一个操作数比较)和条件运算符。 表 1 列出了 Java 语言中的关系和

  • 我正在构建我的第一个VueJs应用程序,我想异步加载我的模板。在我们的框架中,我们将模板存储在数据库中,这就是为什么。它一直在工作,直到我的模板中有一些嵌套的dom元素,而没有任何数据绑定到它为止。所以我的Vuejs就像: 这是工作时,我的模板是这样的: 但如果是这样的话: 我明白了 [Vue warn]:呈现中出错:“TypeError:无法读取未定义的”(在中找到)的属性“0” 但如果是这样的

  • 问题内容: 如何使用AngularJS(在模板中)进行三元运算? 最好在html属性中使用一些属性(类和样式),而不是创建和调用控制器的函数。 问题答案: 更新 :Angular 1.1.5添加了一个三元运算符,因此现在我们可以简单地编写 如果使用的是较早版本的Angular,则有两个选择: 上面的项目2创建具有两个属性的对象。数组语法用于选择名称为true的属性或名称为false的属性,并返回关