C++中自动类型转换到std::string和char*之间的区别

4
作为一项学习练习,我一直在研究C++中的自动类型转换。我知道自动类型转换通常应该避免,但我想通过了解它的工作原理来增加我的C++知识。
我创建了一个StdStringConverter类,可以自动转换为std::string,但编译器(Debian上的g++ 4.3.4)似乎在将对象与实际的std::string进行比较时没有进行转换(请忽略无需传递引用和不必要创建临时对象的情况):
#include <string>

class StdStringConverter
{
public:
    explicit StdStringConverter(std::string name) : m_name(name) {}
    operator const std::string () const { return m_name; }
private:
    std::string m_name;
};

int main()
{
    StdStringConverter converter(std::string("Me"));
    const std::string name = "Me";
    // Next line causes compiler error:
    // no match for 'operator==' in 'converter == name'
    return (converter == name) ? 0 : 1;
}

另一方面,如果我稍微修改它成为一个CStringConverter类,自动转换就会发生,尽管比较char指针可能不是我的本意:

#include <string>

class CStringConverter
{
public:
    explicit CStringConverter(std::string name) : m_name(name) {}
    operator const char* () const { return m_name.c_str(); }
private:
    std::string m_name;
};

int main()
{
    CStringConverter converter(std::string("Me"));
    const char* name = "Me";
    // Next line compiles fine, but they are not equal because the
    // pointers don't match.
    return (converter == name) ? 0 : 1;
}

在这种情况下,std::stringchar*之间的区别是否有特殊性质使编译器不能将它们视为相同的?

请参见 https://dev59.com/sXM_5IYBdhLWcg3wp06l 中的被接受的答案。 - Mark Rushakoff
那个注释对于字符串来说是有用的信息,但并没有涉及到比较问题。 - mmmmmm
3个回答

7
问题是由于std::string实际上是std::basic_string类模板的一个实例。命名空间std中可用的operator==需要两个std::basic_string模板:

template<class charT, class traits, class Allocator>
bool operator==(const basic_string& lhs,
                const basic_string& rhs);

如果这个operator==的版本是针对std::string进行重载的,那么您的代码就没问题了。但事实并非如此,这将需要编译器对std::basic_string的模板参数执行模板参数推导,以便它能够理解转换运算符的返回值是可能匹配的。
然而,编译器不会这样做。我不知道标准的哪一部分明确规定了这一点。但总体思路是这样的:这种转换只适用于非模板类型。
我可以建议的一件事情是,您可以将StdStringConverter放在一个命名空间中,并在该命名空间中为std::string提供一个operator==的版本。这样,当您的编译器发现像那样的表达式时,ADL(Argument Dependent Lookup)就会发挥作用,一切都能正常工作。

#include <string>

namespace n1 {

class StdStringConverter
{
public:
    explicit StdStringConverter(std::string name) : m_name(name) {}
    operator std::string () { return m_name; }
private:
    std::string m_name;
};

bool operator==(std::string const& a, std::string const& b)
{
  return a == b; //EDIT: See Paul's comment on std::operator== here.
}

}

int main()
{
    using namespace n1;
    StdStringConverter converter(std::string("Me"));
    std::string name = "Me";
    return (converter == name) ? 0 : 1;   
}

1
+1,这正是发生的事情。标准中的段落为14.8.2.1,其中列出了参数推导期间可能进行的可能转换。当然,不允许使用用户定义的转换来使推导成功。最终,14.8.1/4允许所有隐式转换发生,如果一个参数不再包含需要被推导的模板参数。 - Johannes Schaub - litb
14.8.3/1 的注脚有进一步的解释(请注意,这只是提供信息而非规范):“由于参数推导过程产生的函数模板具有与调用参数完全匹配或仅在允许的有限转换方式上有所不同的参数,因此允许在推断参数上执行的转换集合受到限制。 非被推导的参数允许完整的转换范围。” - Johannes Schaub - litb
@litb - 感谢提供的参考资料 :) - Leandro T. C. Melo
谢谢您的解释。但是,当我尝试运行您的代码时,出现了段错误:调试器显示“a == b”在递归调用自身。将其更改为“return std::operator==(a, b);”可以按预期工作。如果我确实想这样做,这是否是正确的修复方法? - Paul Stephenson
@Paul Stephenson - 当然,是的! - Leandro T. C. Melo

1
在第一个例子中,这两个比较的类(string和StdStringConverter)在类型转换方面没有得到编译器的任何特殊处理。这意味着您制作的运算符重载甚至没有被触发。编译器查找了operator==重载列表,但没有一个接受一个StdStringConverter,因此它会报错。
在第二个例子中,名称是char*。由于它是一个原始类型,因此编译器会尝试将非原始类型转换为char*。由于您已经覆盖了该函数,它可以工作并且您可以比较地址。
编译器不会在不包括原始类型的操作上进行显式的类型转换。它会尝试使用构造函数来使类型匹配。例如,如果您将您的第一个示例更改为以下内容:
#include <string>

class StdStringConverter
{
public:
    StdStringConverter(std::string name) : m_name(name) {}
    bool operator==(const StdStringConverter &name) { return m_name == name.m_name; }
    operator const std::string () const { return m_name; }
private:
    std::string m_name;
};

int main()
{
    StdStringConverter converter(std::string("Me"));
    const std::string name = "Me";
    // Next line causes compiler error:
    // no match for 'operator==' in 'converter == name'
    return (converter == name) ? 0 : 1;
}

现在程序返回0。由于构造函数不再是显式的,编译器将尝试使用它将字符串转换为StdStringConverter。由于StdStringConverter中现在有一个operator==,所以一切都正常工作。

这个解释是不正确的。将所有使用std::string的地方替换为struct A { int a; };,给A一个全局operator==,它就可以正常工作了。问题不在于std::string是用户定义类型,而在于basic_string的模板operator==(也就是说,string没有自己的operator==,只有从basic_string推导出来的)。 - Steve Jessop
例如,另一种(愚蠢的)编译方法是定义 bool operator==(const std::string &lhs, const std::string &rhs) {return lhs.compare(rhs) == 0;}。然后可以隐式地执行到 std::string 的转换,以便使用这个接受两个字符串的 operator==。 - Steve Jessop

-1

有多个因素。如果您这样更改返回语句

return (std::operator==(name, name)) ? 0 : 1;

它可以编译,尽管显然不会做同样的事情。另一方面

return (std::operator==(converter, name)) ? 0 : 1;

则不行,但提供了一个更有趣的错误消息

no matching function for call to ‘operator==(StdStringConverter&, const std::string&)

这让我想起了operator==是基于basic_string<>模板化的,它还有三个模板参数。如果您在示例中使用int而不是std::string,则编译器不会抱怨。

如何使用std::string获得所需的效果更加有趣...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接