C++,实现函数"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.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中声明的。

相关内容

最新更新