来自Guru of the Week #2。我们有原始功能:
string FindAddr( list<Employee> l, string name )
{
for( list<Employee>::iterator i = l.begin(); // (1)
i != l.end();
i++ )
{
if( *i == name ) // (2)
{
return (*i).addr;
}
}
return "";
}
我添加了虚拟的Employee类:
class Employee
{
string n;
public:
string addr;
Employee(string name) : n(name) {}
Employee() {}
string name() const
{
return n;
}
operator string()
{
return n;
}
};
编译错误:
到位(1):
conversion from ‘std::_List_const_iterator<Employee>’ to non-scalar type ‘std::_List_iterator<Employee>’ requested
到位(2):
no match for ‘operator==’ in ‘i.std::_List_iterator<_Tp>::operator* [with _Tp = Employee]() == name’
要消除第一个,我们将iterator
更改为const_iterator
。而消除第二个错误的唯一方法是编写自己的运算符==。 然而,Herb Sutter写道:
未显示Employee类,但为了使其工作,它必须转换为字符串或转换ctor采用字符串。
但是Employee有一个转换函数和转换构造函数。 GCC版本4.4.3。正常编译,g++ file.cpp
没有任何标记。
应该有隐式转换,它应该有效,为什么不呢?我不希望运算符==,我只是希望它像Sutter所说的那样工作,转换为字符串或转换ctor采用字符串。
答案 0 :(得分:3)
Herb Sutter在这种情况下是错误的(我没有“Exceptional C ++”的副本,但是我希望这本书的GotW条目能够被清理掉。)
但首先,为了解决相关错误,您必须从const
参数声明中删除l
。 (请注意,将iterator
替换为const_iterator
只会模糊问题:您的operator string()
不是const
,这意味着它对于常量对象*i
无法调用)。
解决第一个问题后,您的代码确实无法在
编译if( *i == name )
线。发生这种情况是因为比较std::operator ==
个对象的函数std::string
实际上是由标准库定义为模板函数
template<class charT, class traits, class Allocator>
bool operator==(
const basic_string<charT,traits,Allocator>& lhs,
const basic_string<charT,traits,Allocator>& rhs);
为了使此函数参与重载解析,必须成功推导出其模板参数。这在您的上下文中是不可能的,因为在*i == name
中,一个参数是std::string
而另一个参数是Employee
。模板参数推断失败,因此,此模板函数不考虑重载解析。没有其他候选者,编译器报告错误。
出于这个原因,Herb Sutter声称代码应该在operator string()
类中存在Employee
转换函数时可编译是不正确的。代码可能会使用标准库的某些特定实现进行编译,该实现为std::string
声明了专用的非模板比较运算符,但通常标准库实现不会这样做。
他还提出了另一个毫无根据的主张,坚持认为转换的结果必须是暂时的。实际上Employee
类可以有operator const string &() const
转换函数,它不会创建临时函数(而是返回对数据成员的引用,因为它可以在您的示例中完成)。
最后,如果程序为operator ==
比较声明了专用Employee vs. Employee
,那么他声称转换构造函数将使此代码有效的说法才有效。如果不引入这样的专用运算符,转换构造函数将不会影响此代码的有效性。即在你的例子中,声明Employee(string name)
构造函数没有意义 - 它没有实现任何目标。
答案 1 :(得分:3)
我相信代码可以编译,但不是必需的。请记住,string
是basic_string<char>
的typedef,它本身不是一个类。最小的例子:
template <typename T>
struct basic_string
{
};
typedef basic_string<char> string;
struct Employee
{
operator string() const;
};
template <typename T>
bool operator==(const basic_string<T>& a, const basic_string<T>& b);
// Compilation succeeds when this is uncommented
// bool operator==(const basic_string<char>& a, const basic_string<char>& b);
bool f(const Employee& e, const string& s)
{
return e == s; // error
}
是的,Employee
可以转换为string
,但它不是真的一个string
,所以这还不足以确定哪个模板参数用于operator==
。
如果添加了专门用于operator==(const string&, const string&)
的额外重载,则它可以工作,而另一个标准库实现可能只提供该重载。如果提供,代码将编译,但它是标准C ++不需要的扩展。
编辑:实际上,正如其他人提到的那样,这还不够(const
问题),但即使解决其他问题,这仍然存在,我相信这是你的核心问题的答案。
答案 2 :(得分:1)
免责声明:在我编写并发布此问题时,问题的代码已经改变。代码不再是GOTW代码的引用。这是不同的代码,所谓的编译错误显然也不正确,但我让这个答案(对于原始帖子)支持,因为它主要涉及其他问题(我不打算通过相应的编辑追逐一系列问题的编辑并重新处理这个答案。)
@Vaibhav有already answered核心问题,即转换需要明确表达。
但是,自从引用的GOTW(本周大师)关注不必要的临时工,你的班级Employee
代码,
class Employee
{
string n;
public:
string addr;
Employee(string name) : n(name) {}
Employee() {}
string name() const
{
return n;
}
operator string()
{
return n;
}
};
重复GOTW中讨论的一些陷阱。
在
Employee(string name) : n(name) {}
通过值获取字符串参数对C ++ 11来说很好,因为无论如何都会创建一个副本。但是,您应该 move
将此值添加到成员
Employee(string name) : n(move(name)) {}
然后,你的
operator string()
{
return n;
}
不会出现const
,因此无法在const
对象上调用它,因此必须复制该对象才能调用此运算符。
所以,从技术上讲,做
operator string() const
{
return n;
}
但在设计层面,即使这是错误的。员工不是字符串。一个人希望员工转换为哪个字符串?他或她的名字?员工代码?社会安全号码?
隐式转换通常很麻烦,这就是一个例子。它没有帮助explicit
。由于相关的字符串已经可以通过命名操作获得(这是一件好事),因此它可以简化删除这个转换运算符。
答案 3 :(得分:0)
您从迭代器接收一个Employee对象,因此* i指向一个Employee对象。然后,您需要指向该员工的姓名。
试试这个:
if( (*i).name == name )
答案 4 :(得分:-1)
当您反转比较运算符的操作数时会发生什么?编译器抱怨operator ==
没有List<>
,但是为string
定义了。{/ p>
将if( *i == name )
更改为if( name == *i )
应该有效。