【问题标题】:Why C++ implicit conversion works, but explicit one does not?为什么 C++ 隐式转换有效,而显式转换无效?
【发布时间】:2017-04-15 01:22:54
【问题描述】:

以下代码在 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 库的特殊细微差别之外,您如何编写一个类以使隐式转换起作用而显式转换不起作用?
是否有某种构造函数限定符允许这种行为?

【问题讨论】:

  • 隐式转换的问题是,程序员通常不知道到底发生了什么,只是转换“有效”。您应该调试有效的代码并单步执行该调用,以查看在 string s1 = js; 中发生的实际事件序列,如果它不仅仅是直接调用 string 构造函数(您正在尝试),我不会感到惊讶与您的评论行有关)。
  • 我还没有验证,但它可能使用了模板移动构造函数。 string s2 = move(js); 会编译。
  • 在任何情况下,你都不应该使用 C 风格的强制转换,并且应该避免不必要的强制转换。

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


【解决方案1】:

这是重现相同问题的简化代码:

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,问题变得很明显,因为模板类型推导系统不知道其中哪个是正确答案,因为调用运算符的上下文。在这里,直接初始化必须考虑所有可能的重载。这是基本规则:

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&lt;std::string&gt;(json),但是你失去了像std::string str = json一样隐式转换它的能力。

【讨论】:

  • 很好的答案,很好的解释。谢谢。
【解决方案2】:

我认为是当您使用显式转换时,编译器必须从代码中使用隐式转换时更多的功能中进行选择。 当编译器发现

string s1 = js 

它排除了重载,所有的构造函数和转换都标记为“显式”,因此它会选择一个函数。 相反,当编译器发现时:

string s2 = (string)js ;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-01
    相关资源
    最近更新 更多