С++, любопытная ошибка компилятора при реализации функции `int next (std::string param)`
Я был сильно укушен следующим кодом, на который я потратил много времени на драгоценное время.
#include<string>
int next(std::string param){
return 0;
}
void foo(){
next(std::string{ "abc" });
}
Это приводит к следующей ошибке компилятора (на Visual Studio 2013):
1>------ Build started: Project: sandbox, Configuration: Debug Win32 ------
1> test.cpp
1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility(371): error C2039: 'iterator_category' : is not a member of 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
1> c:\users\ray\dropbox\programming\c++\sandbox\test.cpp(8) : see reference to class template instantiation 'std::iterator_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char>>>' being compiled
1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility(371): error C2146: syntax error : missing ';' before identifier 'iterator_category'
1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility(371): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility(371): error C2602: 'std::iterator_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char>>>::iterator_category' is not a member of a base class of 'std::iterator_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char>>>'
1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility(371) : see declaration of 'std::iterator_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char>>>::iterator_category'
1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility(371): error C2868: 'std::iterator_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char>>>::iterator_category' : illegal syntax for using-declaration; expected qualified-name
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Позже я узнал, что если я изменю свое имя функции от next()
на что-то еще, все будет в порядке. Для меня это означает, что существует конфликт имен, в частности имя next
. Я нахожу это странным, потому что я не использовал ничего вроде using namespace std
. Насколько я знаю, next
не является встроенным ключевым словом С++ (это?). Я посмотрел next
здесь, но он std::next
и, как я уже сказал, я не using namespace std
. Как же этот конфликт произошел? Как предотвратить подобные вещи в будущем? Какие еще имена могут вызвать такой конфликт?
Ответы
Ответ 1
Здесь происходит несколько вещей, взаимодействующих тонко.
Во-первых, неквалифицированный вызов next
с аргументом типа std::string
означает, что как и ваша собственная функция next
, стандартный шаблон функции std::next
находится Аргумент-зависимый поиск (ADL).
После того, как поиск имени нашел ваш ::next
и стандартную библиотеку std::next
, он выполняет разрешение перегрузки, чтобы узнать, какой из них лучше подходит для аргументов, с которыми вы его вызывали.
Определение std::next
выглядит так:
template <class ForwardIterator>
ForwardIterator next(ForwardIterator x,
typename std::iterator_traits<ForwardIterator>::difference_type n = 1);
Это означает, что когда компилятор выполняет разрешение перегрузки, он заменяет тип std::string
на std::iterator_traits<std::string>
.
До С++ 14 iterator_traits
не является SFINAE-совместимым, что означает, что он недействителен для создания экземпляра с типом, который не является итератором. std::string
не является итератором, поэтому он недействителен. Правило SFINAE здесь не применяется, поскольку ошибка не находится в непосредственном контексте, и поэтому использование iterator_traits<T>::difference_type
для любого нетератора T
приведет к жесткой ошибке, а не к ошибке замены.
Ваш код должен корректно работать на С++ 14 или использовать другую стандартную библиотечную реализацию, которая уже предоставляет SFINAE-дружественный iterator_traits
, такой как библиотека GCC. Я считаю, что Microsoft также предоставит SFINAE-дружественный iterator_traits
для следующего крупного выпуска Visual Studio.
Чтобы заставить ваш код работать сейчас, вы можете квалифицировать вызов next
, чтобы ADL не выполнялся:
::next(std::string{ "abc" });
Это говорит о вызове next
в глобальном пространстве имен, а не о любом другом next
, который может быть найден путем поиска неквалифицированных имен.
Ответ 2
(Обновлено по комментариям Джонатана)
Здесь есть два вида: С++ 11 и С++ 14. Еще в 2013 году С++ 11 std::next
не был определен правильно. Он должен применяться к итераторам, но из-за того, что выглядит как надзор, это вызовет серьезные сбои, когда вы передадите ему неитератор. Я считаю, что намерение состояло в том, что SFINAE должно было предотвратить это; std::iterator_traits<X>
должен вызывать ошибки замены.
В С++ 14 эта проблема решена. Определение std::next
не изменилось, но второй аргумент (std::iterator_traits<>
) теперь корректно пуст для неитераторов. Это исключает std::next
из набора перегрузки для неитераторов.
Соответствующая декларация (взятая из VS2013)
template<class _FwdIt> inline
_FwdIt next(_FwdIt _First,
typename iterator_traits<_FwdIt>::difference_type _Off = 1)
Эта функция должна быть добавлена к набору перегрузки, если она может быть создана для данных аргументов.
Функция найдена через зависимый от аргумента поиск и структуру заголовка Microsoft. Они помещают std::next
в <xutility>
, который разделяется между <string>
и <iterator>
Примечание: _FwdIt
и _Off
являются частью пространства имён реализации. Не используйте сами подчеркивания.
Ответ 3
Компилятор нашел стандартную функцию std::next
из-за так называемого зависимого от аргумента поиска, потому что аргумент, используемый в вызове - std::string -, объявлен в пространстве имен std
.
Ответ 4
Фактически std::next()
- это функция, определенная в <iterator>
, которая возвращает следующий итератор, переданный в std::next()
. Ваш код работает на моем компьютере с gcc-4.9.2. Подробнее: http://en.cppreference.com/w/cpp/iterator/next
Код, который я использовал:
#include<string>
#include <iostream>
int next(std::string param){
std::cout<<param<<std::endl;
return 0;
}
void foo(){
next(std::string{ "abc" });
}
int main()
{
foo();
return 0;
}
Также на ideone: http://ideone.com/QVxbO4