2017-04-15 58 views
7

下面的代码编译成功在C++ 11:为什么C++隐式转换工作,但明确的转换不工作?

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

它包括JSON for Modern C++。一个工作示例在this wandbox

JSON变量js隐式转换为字符串。但是,如果我取消注释最后一行,即显式转换,则无法编译。汇编结果here

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

+0

我隐式转换的问题是,程序员往往不知道到底发生了什么,只是转换“有效”。你应该调试那些可以工作的代码,并在那个调用中查看发生在'string s1 = js;'中的事件的实际顺序;'如果它不仅仅是直接调用'string'构造函数,我不会感到惊讶你正在试图用你的评论线)。 – PaulMcKenzie

+0

我还没有验证,但它可能使用模板移动构造函数。 'string s2 = move(js);'编译。 – Phil1970

+0

无论如何,你不应该使用C风格的演员,你应该避免不必要的演员。 – Phil1970

回答

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 
} 

我们可以看到编译错误是相似的:

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; 

问题依赖于通用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的情况下,它有很多构造函数,但是如果您执行object = other形式的copy-initialization(1),T推导为左侧对象的类型(即std::string)。

铸造是另一回事。看,这是同样的问题,如果您尝试使用第三种形式(在这种情况下是direct initialization)来复制初始化:

R r(s); // same ambiguity error 

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

R() = default; 
R(const R&) = default; 
R(R&&) = default; 
R(int) {} 

鉴于R的构造可以采用任何其他R,或int,这个问题变得很明显,作为模板类型推演系统不知道其中哪一个是正确的答案,由于在上下文操作员从中被调用。在这里,直接初始化必须考虑所有可能的重载。这里的基本规则:

一个是必需的,因为转换的结果类型。 P是返回类型转换功能模板的

在这种情况下:

R r = s; 

R是必需的,因为转化率()的结果的类型。但是,您能否告诉我哪种类型的A将在下面的代码中表示?现在

R r(s); 

背景下具有Rint作为选项,因为R中一个构造函数整数。但是转换类型只需要推导为其中的一种。 R是一个有效的候选人,因为至少有一个构造函数需要Rint也是一个有效的候选人,因为有一个构造函数也是一个整数。没有优胜者候选人,因为他们都同样有效,因此含糊不清。

当您将您的json对象转换为std::string时,情况完全相同。有一个构造函数接受一个字符串,另一个接受一个分配器。两个重载都是有效的,所以编译器不能选择一个。

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

+0

很好的答案,很好的解释。谢谢。 – GetFree

2

我认为这是当你使用显式转换时,编译器必须从更多的函数中选择当代码使用隐式转换。 当编译器发现

string s1 = js 

从超载排除,所有costructor和转换标有“明确的”,因此它的结果选择一个功能。 相反,当编译器发现:

string s2 = (string)js ; 

它必须包括所有的转换,然后歧义。