【问题标题】:Classes with both template and non-template conversion operators in the condition of switch statementswitch 语句条件下同时具有模板和非模板转换运算符的类
【发布时间】:2014-09-22 16:51:04
【问题描述】:

问题最初出现在this question。考虑以下代码:

class Var
{
public:

    operator int () const
    { return 0; }

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

};

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

没有operator int() const { return 0; },g++ 和clang reject 的代码。

但是,上面带有operator int() 的代码是clang 的accepted,而g++ 的rejected 则出现以下错误:

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

哪个编译器是正确的?

【问题讨论】:

  • switch语句可以切换多种类型,不仅仅是int
  • 从标准的措辞看来,不应执行重载解析,并且实际上只需要一个可能的函数来执行转换。这将使 g++ 正确。但是,我不确定。
  • 我更新了我的答案,我发现N3323 涵盖了围绕上下文转换 的逻辑,它强烈指出clang 在这个问题上是正确的。
  • @T.C. , ShafikYaghmour 好吧,如果我们看一下 clang++、g++ 和 msvc++ 这三个编译器,它们各自的“C++11”模式(或者,msvc++ 的默认模式)包含了一些针对标准中缺陷的建议解决方案。如果我们仅将 C++11 视为已作为标准发布的内容,那么 N3485 就不是“C++11 草案”。如果我们看一下 C++11 的预期,或者有修复但没有添加,我确实认为 N3485 可以用来谈论 C++11。无论如何,C++11 的含义有多种定义。
  • 我为此提交了gcc bug report,抄送@dyp

标签: c++ c++11 language-lawyer implicit-conversion c++14


【解决方案1】:

6.4.2/2 switch 声明(强调我的)

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

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

【讨论】:

  • 但问题是 gcc 并没有考虑到非模板转换运算符与模板版本相比更好的匹配,而 clang 则考虑。您的回答没有解决这部分问题。
  • @Praetorian 标准说“一个 single 非显式转换函数”。它并没有说有重载决议。
  • 另一方面,可以合理地论证模板转换函数不算“到整数或枚举类型”,尤其是T不能被推断为任何整数或枚举类型,在这种情况下,只有一个符合条件的转换函数。
  • 我通过电子邮件发送了 C++ 标准讨论邮件列表。让我们看看他们的想法:-)
  • 模板是否隐式转换为整数类型? --当然:int x = v; char c = v; ...。无法推断出类型这一事实并不能使其成为非转换。请注意,引用并没有说switch 声明的上下文中 或类似的内容,它只关心它的存在。
【解决方案2】:

根据我的拙见并基于§13.3.3/1 最佳可行函数 [over.match.best],非模板重载转换运算符(即operator int() const)具有在重载决议挑选方面的优先级高于其模板对应项(即template &lt;typename T&gt; operator T () const)。

因此,重载解析会正确选择operator int() const 而不是template &lt;typename T&gt; operator T () const,因为这是最好的可行函数。

此外,由于将选择非模板版本而不是模板版本(即,模板不会被编译器具体化/限定),class Var 将只有一个转换函数,因此 §6.4.2/2 将满足单整数转换的 switch 语句 [stmt.switch]。

因此,Clang 是对的,而 GCC 是错的。

【讨论】:

  • 我认为非模板对通过模板实例化选择的等效签名的重载解决偏好与此处无关。
【解决方案3】:

如果我在重载方面正确阅读本节,Clang 是正确的

13.3.3 最佳可行函数[over.match.best]

[...] 鉴于这些定义,如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列,则可行函数 F1 被定义为比另一个可行函数 F2 更好的函数,然后 [...]

— F1 是非模板函数,F2 是函数模板特化,或者,如果不是,[...]

草稿可免费阅读。不确定 13.3.3 中的任何更改是否已纳入最终规范(我还没有付钱)

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

我会提交一个 G++ 错误 :-) 他们可能会以标准的不同部分进行反击以证明其合理性,但它似乎不符合标准。

编辑 aschepler 的评论:

发件人:http://publib.boulder.ibm.com/infocenter/comphelp/v101v121/index.jsp?topic=/com.ibm.xlcpp101.aix.doc/language_ref/cplr315.html

假设 f 是一个重载的函数名。当您调用重载函数 f() 时,编译器会创建一组候选函数。这组函数包括所有名为 f 的函数,可以从调用 f() 的位置访问这些函数。编译器可能会包含一个名为 f 的可访问函数的替代表示作为候选函数,以促进重载解析。

在创建了一组候选函数之后,编译器创建了一组可行的函数。这组函数是候选函数的子集。每个可行函数的参数数量与您用来调用 f() 的参数数量一致。

【讨论】:

  • 我认为模板转换一开始是不可行的。
  • 可行的意思是在过载匹配的上下文中特定的东西。
  • 是的,确实如此。 13.3.1/7:“在候选函数模板的每种情况下,候选函数模板特化是使用模板参数推导生成的。然后以通常的方式将这些候选函数作为候选函数处理。”但是这里的模板参数推导会失败,所以候选集没有特化,可行函数集是候选集的子集。
  • 至少,我会在 9 月的 CPPcon 上提问并在这里发帖,除非他们在那之前回复邮件列表 :-)
【解决方案4】:

我相信clang 在这里是正确的。

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

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

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

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

这不引用8.5 部分,它允许通过专门引用13.3 部分来实现重载解析,而不允许我们无法使用的重载解析:

template <typename T>
operator T () const

因此没有歧义。

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

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

它特别允许重载解决方案,并直接参考涵盖此内容的13.3 部分。这是有道理的,因为我们有一个特定的目标类型 bool 可以转换为我们在 switch 情况下没有的。

为什么

我们可以通过查看 N3323: A Proposal to Tweak Certain C++ Contextual Conversions, v3 来解决这个问题,它涵盖了这个问题。引用整篇论文会很困难,所以我将尝试引用足够多的上下文。它说:

C++ 表达式出现的上下文通常会影响 表达式被评估,因此可能对 表达式以确保这样的评估是可能的。 [...]

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

包括:

[stmt.switch]/2:“条件应为整型,枚举 类型,或具有单个非显式转换的类类型 存在整数或枚举类型的函数(12.3)。”

然后说:

主要问题,在本文中引用的四种情况中的每一种情况下 介绍,似乎在于他们的共同点很有帮助但很严格 将一个类限制为只有一个转换运算符的要求 [...]

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

当前的语言在这一点上似乎不清楚。也是 不清楚产生对 适当的类型是适当的转换运算符。 (关于一个问题 这一点已于 2011-02-21 发布到核心反射器,但 在撰写本文时尚未得到答复。)当前的编译器实践似乎 承认这样的运营商,但目前的语言似乎没有。

并提议:

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

新语言如下;

某些其他语言结构需要类似的转换,但要转换为 具有适合于的一组指定类型之一的值 构造。出现在这种上下文中的类类型 E 的表达式 e 据说被上下文隐式转换为指定类型 T and 是良构的当且仅当 e 可以隐式转换为 a 类型 T 确定如下: E 搜索转换 返回类型为 cv T 或对 cv T 的引用使得 T 上下文允许。应该有一个这样的T。

注意N3486: C++ Editor's Report, October 2012 向我们展示了N3323 何时被纳入标准草案。

更新

提交了gcc bug report

【讨论】:

    【解决方案5】:

    以下是相关引述,但最终答案在很大程度上取决于解释。我现在什至无法决定最喜欢的。

    N3797 6.4.2/2:

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

    4/5:

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

    14.5.2/6:

    名称查找未找到转换函数的特化。相反,考虑使用上下文中可见的任何转换函数模板。对于每个这样的运算符,如果参数推导成功 (14.8.2.3),则使用结果特化,就像通过名称查找找到一样。

    14.5.2/8:

    重载解析 (13.3.3.2) 和偏序 (14.5.6.2) 用于在转换函数模板和/或非模板转换函数的多个特化中选择最佳转换函数。

    解释 1:4/5 说“转换函数”,而不是“转换函数和转换函数模板”。所以Var::operator int() const是唯一的选择,clang是正确的。

    解释 2 [弱?]:14.5.2 要求我们通过重载决议和偏序比较转换函数模板,与非模板转换函数处于相同的初始位置。那些比较函数模板特化和函数,而不是函数模板,所以我们将做模板参数推导。转换函数模板的模板参数推导需要目标类型。尽管我们通常有一个更清晰的目标类型,但在这种情况下,我们将尝试(无论如何理论上)允许的类型集中的所有类型。但很明显,非模板函数是比所有模板特化更好的可行函数,因此重载决议选择了非模板函数。叮当是正确的。

    解释3:由于重载解析需要模板实参推导,而模板实参推导需要已知的目标类型,所以必须先考虑4/5的语义,然后才能将其转换后的类型(如果有)用于重载解决过程。 14.5.2 要求考虑转换函数模板,但随后我们发现有多个有效类型T,我们有一个到T 的转换函数[该函数可能是函数模板特化]。该程序格式不正确,因此 g++ 是正确的。

    【讨论】:

      【解决方案6】:

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

      gcc 是正确的,因为标准要求将单个非显式转换运算符转换为 switch 中使用的类型的整数或枚举类型。

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

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

      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;
        }
      }
      

      上面的代码演示了一个例子,我们有无限数量的enums 隐式可用。我们有一个operator T,当且仅当T::bob==2 时,它将转换为类型T。现在,我们的程序中没有这样的enums(即使我们删除了3+,仍然没有,因为它不是enum class——很容易纠正)。

      所以test 只能转换为int,因此switch 语句应该编译。 gcc 未能通过此测试,并声称 template operator T 使其模棱两可(自然而然地没有告诉我们 T 是什么)。

      enum type 替换为enum class type,并删除3+ 会使switch 语句在标准下非法。但是要让编译器弄清楚这一点,它基本上必须实例化程序中所有可能的模板,以寻找具有相关属性的秘密enum。通过一些工作,我可以强制编译器解决 NP 完全问题(或者,排除编译器限制,停止问题)以确定程序是否应该编译。

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

      【讨论】:

      • operator T() 怎么能附加 SFINAE 子句,并且仍然可用?
      • @aschepler template&lt;class T,class=std::enable_if_t&lt;T::bob==2&gt;&gt; operator T()
      • @aschepler 嗯。无法访问的operator long&lt;long,void&gt;() 可能会破坏我的计划:它存在,SFINAE 无法阻止它的存在。因此,一旦概念精简版到来,这可能只会抬起丑陋的头脑。嗯。
      • operator long&lt;long, void&gt;() 到底有什么问题?有模板参数使得替换成功吗? template&lt;class T, std::enable_if_t&lt;T::bob==2&gt;* = nullptr&gt; operator T()呢?
      • @T.C.呵呵。这可能就是为什么 Shafik 是对的,就像当当一样。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多