Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++, curious compiler error when implementing a function `int next(std::string param)`

I've been badly bitten by the following code, on which I wasted many hours of precious time.

#include<string>

int next(std::string param){
    return 0;
}

void foo(){
    next(std::string{ "abc" });
}

This produces the following compiler error (on 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 ==========

I found out later that if I change my function name from next() to something else, all is fine. To me, this indicates that there is a name conflict, specifically of the name next. I find this strange because I didn't use anything like using namespace std. As far as I know, next is not a built-in C++ keyword (is it?). I looked up next here, but it's std::next and as I said I didn't using namespace std. So how did this conflict happen? How do I prevent similar things in the future? What other names might cause a conflict like this?

like image 928
Ray Avatar asked Apr 12 '15 21:04

Ray


4 Answers

There are several things going on here, interacting in subtle ways.

Firstly, an unqualified call to next with an argument of type std::string means that as well as your own next function, the standard function template std::next is found by Argument-dependent lookup (ADL).

After name lookup has found your ::next and the standard library's std::next it performs overload resolution to see which one is a better match for the arguments you called it with.

The definition of std::next looks like:

template <class ForwardIterator>
  ForwardIterator next(ForwardIterator x,
  typename std::iterator_traits<ForwardIterator>::difference_type n = 1);

This means that when the compiler performs overload resolution it substitutes the type std::string into std::iterator_traits<std::string>.

Prior to C++14 iterator_traits is not SFINAE-friendly which means that it is invalid to instantiate it with a type that is not an iterator. std::string is not an iterator, so it's invalid. The SFINAE rule does not apply here, because the error is not in the immediate context, and so using iterator_traits<T>::difference_type for any non-iterator T will produce a hard error, not a substitution failure.

Your code should work correctly in C++14, or using a different standard library implementation that already provides a SFINAE-friendly iterator_traits, such as GCC's library. I believe Microsoft will also provide a SFINAE-friendly iterator_traits for the next major release of Visual Studio.

To make your code work now you can qualify the call to next so ADL is not performed:

::next(std::string{ "abc" });

This says to call the next in the global namespace, rather than any other next that might be found by unqualified name lookup.

like image 140
Jonathan Wakely Avatar answered Oct 30 '22 14:10

Jonathan Wakely


(Updated per Jonathan's comments) There are two views here, the C++11 and the C++14 view. Back in 2013, C++11's std::next was not properly defined. It is supposed to apply to iterators, but due to what looks like an oversight it will cause hard failures when you pass it a non-iterator. I believe the intention was that SFINAE should have prevented this; std::iterator_traits<X> should cause substitution failures.

In C++14, this problem is solved. The definition of std::next hasn't changed, but it's second argument (std::iterator_traits<>) is now properly empty for non-iterators. This eliminates std::next from the overload set for non-iterators.

The relevant declaration (taken from VS2013) is

template<class _FwdIt> inline
 _FwdIt next(_FwdIt _First,
 typename iterator_traits<_FwdIt>::difference_type _Off = 1)

This function should be added to the overload set if it can be instantiated for the given arguments.

The function is found via Argument Dependent Lookup and Microsoft's header structure. They put std::next in <xutility> which is shared between <string> and <iterator>

Note: _FwdIt and _Off are part of the implementation namespace. Don't use leading underscores yourself.

like image 21
MSalters Avatar answered Oct 30 '22 16:10

MSalters


Actually std::next() is a function defined in <iterator> which returns the next iterator passed to std::next(). Your code is running on my computer with gcc-4.9.2. More : http://en.cppreference.com/w/cpp/iterator/next

The code I used:

#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;
}

Also on ideone : http://ideone.com/QVxbO4

like image 33
0x6773 Avatar answered Oct 30 '22 14:10

0x6773


The compiler found standard function std::next due to the so-called Argument Dependent Lookup because the argument used in the call - std::string - is declared in namespace std.

like image 36
Vlad from Moscow Avatar answered Oct 30 '22 15:10

Vlad from Moscow