为什么C++隐式转换有效,但显式转换不起作用?

Get*_*ree 7 c++ casting implicit-conversion explicit-conversion c++11

以下代码在C++ 11中成功编译:

#include "json.hpp"
using json = nlohmann::json ;

using namespace std ;

int main(){
    json js = "asd" ;
    string s1 = js ; // <---- compiles fine
    //string s2 = (string)js ; // <---- does not compile
}
Run Code Online (Sandbox Code Playgroud)

它包括JSON for Modern C++.一个工作示例就在这个wandbox中.

JSON变量js隐式转换为字符串.但是,如果我取消注释最后一行,这是一个显式转换,它就无法编译.编译结果在这里.

除了这个json库的特殊细微差别,你如何编写一个类,以便隐式转换工作,但一个明确的转换不起作用?
是否有某种构造函数限定符允许此行为?

Már*_*ldi 8

这是一个简化的代码,可以重现同样的问题:

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
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到编译错误类似:

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;
Run Code Online (Sandbox Code Playgroud)

问题依赖于泛型S::operator T(),它会愉快地将值返回到您想要的任何类型.例如,分配s给任何类型都可以:

int i = s; // S::operator T() returns int{};
std::string str = s; // S::operator T() returns std::string{};
Run Code Online (Sandbox Code Playgroud)

T被推导为转换类型.在这种情况下std::string,它有很多构造函数,但如果你进行表单 的复制初始化(1)object = other,T则推导出左手对象的类型(即std::string).

铸造是另一回事.请注意,如果您尝试使用第三种形式(在本例中是直接初始化)进行复制初始化,则会出现同样的问题:

R r(s); // same ambiguity error
Run Code Online (Sandbox Code Playgroud)

好的,R再次构造函数重载是什么?

R() = default;
R(const R&) = default;
R(R&&) = default;
R(int) {}
Run Code Online (Sandbox Code Playgroud)

鉴于R构造函数可以采用另一个R,或者int问题变得明显,因为模板类型推导系统由于调用运算符的上下文而不知道这些中的哪一个是正确的答案.这里,直接初始化必须考虑所有可能的重载.这是基本规则:

A是转换结果所需的类型.P是转换函数模板的返回类型

在这种情况下:

R r = s;
Run Code Online (Sandbox Code Playgroud)

R是转换结果所需的类型(A).但是,你能告诉我在下面的代码中代表哪种类型的A

R r(s);
Run Code Online (Sandbox Code Playgroud)

现在上下文有Rint作为选项,因为R中有一个带有整数的构造函数.但转换类型只需要推导出其中一种.R是一个有效的候选者,因为至少有一个构造函数需要一个R.int也是一个有效的候选者,因为有一个构造函数也采用整数.没有获胜者候选人,因为他们两个都同样有效,因此含糊不清.

当您将json对象转换为a时std::string,情况完全相同.有一个构造函数接受一个字符串,还有另一个接受一个分配器.两个重载都是有效的,因此编译器无法选择一个.

如果将转换运算符标记为,则问题将消失explicit.这意味着你可以做到std::string str = static_cast<std::string>(json),但是你失去了隐式转换它的能力std::string str = json.