Подтвердить что ты не робот

С++, любопытная ошибка компилятора при реализации функции `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. Как же этот конфликт произошел? Как предотвратить подобные вещи в будущем? Какие еще имена могут вызвать такой конфликт?

4b9b3361

Ответ 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