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

时间:2017-04-15 01:22:55

标签: c++ c++11 casting implicit-conversion explicit-conversion

以下代码在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
}

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

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

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

2 个答案:

答案 0 :(得分: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会推导到左侧对象&# 39; s类型(std::string)。

施法是另一回事。如果您尝试使用第三种形式(在本例中为direct initialization)进行复制初始化,请注意同样的问题:

R r(s); // same ambiguity error

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

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

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

  

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

在这种情况下:

R r = s;

R是转化结果所需的类型( A )。但是,您能否告诉以下代码中 A 代表哪种类型?

R r(s);

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

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

如果转换运算符标记为explicit,问题就会消失。这意味着您可以std::string str = static_cast<std::string>(json),但是您无法像std::string str = json那样隐式转换它。

答案 1 :(得分:2)

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

string s1 = js 

它排除了重载,所有的结构和转换标记为&#34; explicit&#34;所以它导致选择一个功能。 相反,当编译器找到:

string s2 = (string)js ;

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