14

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

#include <string>

class Foo
{
public:
  explicit operator std::string() const;
};

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

铿锵++ 说:

foo.cpp:9:13: error: no matching constructor for initialization of 'std::string'
      (aka 'basic_string<char>')
std::string bar{Foo{}};
            ^  ~~~~~~~
...: 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.

4

3 回答 3

4

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

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

  • [..]
  • 如果没有找到可行的初始化列表构造函数,则再次执行重载决议,其中候选函数是类的所有构造函数,T参数列表由初始化列表的元素组成。

由于第二行编译得很好,因此存在不一致:当涉及到重载决议时,它们应该是等价的。

于 2014-12-19T21:37:44.770 回答
3

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

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

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

于 2014-12-19T21:38:23.770 回答
2

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

首先,直接初始化

std::string baz(Foo{});

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

直接列表初始化

std::string bar{Foo{}};

另一方面,不考虑任何用户定义的转换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 在所有情况下,仅考虑标准转换序列和省略号转换序列

于 2014-12-19T23:44:31.360 回答