C ++,在实现函数`int next(std :: string param)`时出现奇怪的编译器错误

C++, curious compiler error when implementing a function `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 不是内置的 C++ 关键字(是吗?)。我查了 next here,但它是 std::next,正如我所说,我没有 using namespace std。那么这场冲突是如何发生的呢?我如何防止将来发生类似的事情?还有哪些其他名称可能会导致这样的冲突?

由于所谓的参数相关查找,编译器找到了标准函数 std::next,因为调用中使用的参数 - std::string - 在命名空间 std 中声明。

实际上 std::next()<iterator> 中定义的函数,returns 下一个迭代器传递给 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

(根据乔纳森的评论更新) 这里有两个视图,C++11 和 C++14 视图。早在 2013 年,C++11 的 std::next 就没有被正确定义。它应该适用于迭代器,但由于看起来像是疏忽,当您将它传递给非迭代器时,它会导致硬故障。我相信意图是 SFINAE 应该阻止这种情况; std::iterator_traits<X> 应该会导致替换失败。

在C++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)

如果可以为给定参数实例化此函数,则应将其添加到重载集中。

函数是通过Argument Dependent Lookup 和Microsoft 的头结构找到的。他们将 std::next 放在 <xutility> 中,后者由 <string><iterator>

共享

注意:_FwdIt_Off 是实现命名空间的一部分。不要自己使用前导下划线。

这里发生了几件事,以微妙的方式相互作用。

首先,使用 std::string 类型的参数对 next 的非限定调用意味着除了您自己的 next 函数外,标准函数模板 std::next 是由 Argument-dependent lookup (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>.

在 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 之前,因此对任何非迭代器 T 使用 iterator_traits<T>::difference_type 将产生硬错误,而不是替换失败。

您的代码应该可以在 C++14 中正常工作,或者使用已经提供 SFINAE 友好 iterator_traits 的不同标准库实现,例如 GCC 的库。我相信微软也会为 Visual Studio.

的下一个主要版本提供对 SFINAE 友好的 iterator_traits

现在为了让您的代码正常工作,您可以限定对 next 的调用,这样就不会执行 ADL:

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

这表示调用全局命名空间中的 next,而不是可能通过非限定名称查找找到的任何其他 next