我被下面的代码咬得很厉害,在上面我浪费了很多小时的宝贵时间。
#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.0vcincludexutility(371): error C2039: 'iterator_category' : is not a member of 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
1> c:usersraydropboxprogrammingc++sandboxtest.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.0vcincludexutility(371): error C2146: syntax error : missing ';' before identifier 'iterator_category'
1>c:program files (x86)microsoft visual studio 12.0vcincludexutility(371): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>c:program files (x86)microsoft visual studio 12.0vcincludexutility(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.0vcincludexutility(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.0vcincludexutility(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
,但它是std::next
,正如我所说,我没有using namespace std
。那么,这场冲突是如何发生的呢?我如何防止将来发生类似的事情?其他哪些名称可能会引发这样的冲突?
这里发生了一些事情,以微妙的方式进行交互。
首先,使用类型为std::string
的参数对next
进行非限定调用意味着,除了您自己的next
函数外,还可以通过参数相关查找(ADL)找到标准函数模板std::next
。
在名称查找找到您的::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
对SFINAE不友好,这意味着用非迭代器的类型实例化它是无效的。std::string
不是迭代器,因此它是无效的。SFINAE规则在此不适用,因为错误不在即时上下文中,因此对任何非迭代器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
。
(根据Jonathan的评论更新)这里有两个视图,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)
如果可以为给定的参数实例化此函数,则应将其添加到重载集。
该函数可通过参数相关查找和Microsoft的标头结构找到。他们把std::next
放在<xutility>
中,<string>
和<iterator>
共享
注意:_FwdIt
和_Off
是实现名称空间的一部分。不要自己使用前导下划线。
实际上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
编译器发现标准函数std::next
是由于所谓的参数依赖查找,因为调用中使用的参数std::string是在命名空间std
中声明的。