От Гуру недели № 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
, И единственный способ устранить вторую ошибку — написать собственный оператор ==. Тем не менее, Херб Саттер писал, что:
Класс Employee не отображается, но чтобы это работало, он должен иметь либо преобразование в строку, либо преобразователь ctor, принимающий строку.
Но у Employee есть функция преобразования и конструктор преобразования. GCC версия 4.4.3. Скомпилировано нормально, g++ file.cpp
без каких-либо флагов.
Должно быть неявное преобразование, и оно должно работать, почему это не так? Я не хочу оператора ==, я просто хочу, чтобы он работал, как сказал Саттер, с преобразование в строку или преобразователь ctor, принимающий строку.
Херб Саттер ошибается в этом случае (у меня нет копии «Исключительного 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
, Сбой вывода аргумента шаблона, и по этой причине эта функция шаблона не рассматривается для разрешения перегрузки. Не имея других кандидатов, компилятор сообщает об ошибке.
По этой причине заявление Херба Саттера о том, что код должен быть компилируемым при наличии operator string()
функция преобразования в Employee
класс неверен. Код может скомпилироваться с какой-то конкретной реализацией стандартной библиотеки, которая объявляет специальный оператор сравнения без шаблона для std::string
, но обычно реализации стандартной библиотеки не делают этого таким образом.
Он также делает еще одно необоснованное утверждение, настаивая на том, что результат этого обращения должен быть временным. В действительности Employee
класс может иметь operator const string &() const
функция преобразования, которая не создаст временные объекты (вместо этого верните ссылку на элемент данных, как это можно сделать в вашем примере).
Наконец, его утверждение, что конструктор преобразования заставит этот код работать, верно только в том случае, если программа объявляет выделенный operator ==
за Employee vs. Employee
сравнения. Без введения такого выделенного оператора конструктор преобразования не будет влиять на достоверность этого кода. То есть в вашем примере не было никакого смысла декларировать Employee(string name)
конструктор — ничего не добивается.
Я считаю, что код разрешено компилировать, но не обязательно. Помни что string
является typedef для basic_string<char>
это не класс сам по себе. Минимальный пример:
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
проблемы), но даже если устранить другие проблемы, это остается, и я считаю, что это ответ на ваш основной вопрос.
отказ: код вопроса уже изменился к тому времени, как я написал и опубликовал это. Код больше не является цитатой из кода GOTW. Это другой код, и предполагаемая ошибка компиляции, по-видимому, также неверна, но я оставляю этот ответ (на исходную публикацию) в силе, поскольку он в основном касается других вопросов (я не собираюсь гоняться за серией правок в вопросе, соответственно редактируя и дорабатываю этот ответ).
@Vaibhav имеет уже ответил основной вопрос, а именно, что преобразование должно быть явно выражено.
Но так как цитируемый 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
, Поскольку соответствующая строка уже доступна через именованную операцию (что хорошо), это значительно улучшит класс Удалить это оператор преобразования.
Вы получаете объект Employee от итератора, поэтому * i указывает на объект Employee. Затем вам нужно указать на имя этого сотрудника.
Попробуй это:
if( (*i).name == name )
Что происходит, когда вы меняете операнды операторов сравнения? Компилятор жалуется, что нет operator ==
за List<>
, но это определено для string
,
изменения if( *i == name )
в if( name == *i )
должно сработать.