大括号初始值设定项列表中是不是允许显式转换运算符?

Posted

技术标签:

【中文标题】大括号初始值设定项列表中是不是允许显式转换运算符?【英文标题】:Are explicit conversion operators allowed in braced initializer lists?大括号初始值设定项列表中是否允许显式转换运算符? 【发布时间】:2014-12-19 21:10:26 【问题描述】:

以下代码可以使用 GCC 4.9.2 编译,但不能使用 Clang 3.5.0:

#include <string>

class Foo

public:
  explicit operator std::string() const;
;

std::string barFoo; // Works in g++, fails in clang++
std::string baz(Foo); // Works in both

clang++ 说:

foo.cpp:9:13: error: no matching constructor for initialization of 'std::string'
      (aka 'basic_string<char>')
std::string barFoo;
            ^  ~~~~~~~
...: note: candidate constructor not viable: no known conversion from 'Foo' to
      'const std::basic_string<char> &' for 1st argument
      basic_string(const basic_string& __str);
      ^

奇怪的是,如果将 std::string 替换为像 int 这样的原始类型,它会起作用。

【问题讨论】:

gcc.gnu.org/bugzilla/show_bug.cgi?id=51553 和链接的 clang 非 bug 可能是相关的 【参考方案1】:

这似乎是一个 Clang 错误。 [over.match.list]/1:

当非聚合类类型T的对象被列表初始化时 (8.5.4),重载决议分两个阶段选择构造函数:

[..] 如果没有找到可行的初始化列表构造函数,重载解析再次执行,其中候选函数都是 T 类的构造函数和参数列表包括 初始化列表的元素。

由于第二行编译良好,因此存在不一致:在重载决议方面它们应该是等价的。

【讨论】:

【参考方案2】:

来自 [class.conv.fct]/2:

转换函数可能是显式的 (7.1.2),在这种情况下,它仅被视为用于直接初始化 (8.5) 的用户定义转换。

所以问题是你如何初始化你的对象。显然baz 是直接初始化的,所以这行得通。相比之下,bar 是直接列表初始化的,但不是直接初始化的,因此无法进行显式转换。

【讨论】:

我认为它在 Clang 中也因非显式转换功能而失败 @PiotrS。哇,你是对的,这太疯狂了。这绝对是一个 Clang 错误,对吧? @TavianBarnes 我不知道,我也很好奇【参考方案3】:

clang 似乎并不关心转换运算符是否为explicit,并且由于[over.best.ics] 中的措辞,我相信它是正确的。

首先,直接初始化

std::string baz(Foo);

适用于 gcc 和 clang,并由 KerrekSB's answer 中提到的 [class.conv.fct]/2 解释。

直接列表初始化

std::string barFoo;

另一方面,不考虑任何用户定义的转换,explicit 与否。

引用 N3337,§13.3.3.1/4 [over.best.ics]

但是,考虑构造函数的参数时或用户定义的转换函数是 13.3.1.3 的候选者,当在类的第二步中调用以复制/移动临时对象时复制初始化,13.3.1.7 将初始化列表作为单个参数传递或初始化列表只有一个元素并且转换为某个类 X 或引用(可能是 cv 限定的)X 考虑X的构造函数的第一个参数,或者13.3.1.4、13.3.1.5或13.3.1.6在所有情况下,只考虑标准转换序列和省略号转换序列

【讨论】:

这部分是否有可能专门讨论初始化列表(即std::initializer_list),而不是一般的括号初始化列表? 我想如果你是对的,那么当std::string 被替换为int 时,clang 接受代码是错误的? @Tavian 不,我很确定这意味着支撑初始化列表,因为其中提到的 13.3.1.7 通常涵盖列表初始化。我不知道你的第二个问题的答案。引用的段落提到了class X,所以也许这意味着它只适用于用户定义的类型?您应该将 int 观察添加到您的 clang 错误报告中,这样您就可以得到两者的答案。

以上是关于大括号初始值设定项列表中是不是允许显式转换运算符?的主要内容,如果未能解决你的问题,请参考以下文章

无法从大括号括起来的初始值设定项列表转换

为啥在使用大括号初始值设定项列表时首选 std::initializer_list 构造函数?

C++ 不能用初始值设定项列表初始化非聚合错误

为啥 C# 3.0 对象初始值设定项构造函数括号是可选的?

如何将 std::map::operator= 与初始值设定项列表一起使用

显式复制构造函数和统一初始化