这是重现相同问题的简化代码:
struct S
{
template <typename T>
operator T() // non-explicit operator
{ return T{}; }
};
struct R
{
R() = default;
R(const R&) = default;
R(R&&) = default;
R(int) {} // problematic!
};
int main()
{
S s{};
R r = static_cast<R>(s); // error
}
我们可以看到编译错误类似:
error: call of overloaded 'R(S&)' is ambiguous
R r = static_cast<R>(s);
^
note: candidates...
R(int) {}
R(R&&) = default;
R(const R&) = default;
问题依赖于 generic S::operator T()
,它会很高兴地将值返回为您想要的任何类型。例如,分配s
给任何类型都可以:
int i = s; // S::operator T() returns int{};
std::string str = s; // S::operator T() returns std::string{};
T
被推断为转换类型。在 的情况下std::string
,它有很多构造函数,但是如果您对表单 进行复制初始化(1)object = other
,T
则推断为左侧对象的类型(即std::string
)。
铸造是另一回事。看,如果您尝试使用第三种形式进行复制初始化(在这种情况下是直接初始化),则会出现同样的问题:
R r(s); // same ambiguity error
好的,构造函数重载R
又是什么?
R() = default;
R(const R&) = default;
R(R&&) = default;
R(int) {}
鉴于R
' 的构造函数可以采用另一个R
或int
,问题变得很明显,因为模板类型推导系统由于调用运算符的上下文不知道其中哪一个是正确答案。在这里,直接初始化必须考虑所有可能的重载。这是基本规则:
A是转换结果所需的类型。P是转换函数模板的返回类型
在这种情况下:
R r = s;
R
是转换结果所需的类型 ( A )。但是,您能说出下面的代码中A将代表哪种类型吗?
R r(s);
现在上下文具有R
和int
作为选项,因为 R 中有一个接受整数的构造函数。但是转换类型只需要推导到其中一种即可。R
是一个有效的候选者,因为至少有一个构造函数采用R
. int
也是一个有效的候选者,因为有一个构造函数也采用整数。没有获胜者候选人,因为它们都同样有效,因此模棱两可。
当您将 json 对象转换为std::string
时,情况完全相同。有一个接受字符串的构造函数,还有一个接受分配器的构造函数。两个重载都是有效的,所以编译器不能选择一个。
如果将转换运算符标记为 ,问题就会消失explicit
。这意味着你可以做std::string str = static_cast<std::string>(json)
,但是你失去了像std::string str = json
.