记录一次C++报错

前段时间做C++的练习,遇到了一个非常混乱的报错,在此记录一下。

错误复现

出错的代码是关于std::map的练习。

#include <string>
#include <map>

class PhoneBook {
   public:
	void addContact( const std::string& name, const std::string& phoneNumber ) {
		// 添加联系人
		contacts.insert(name,phoneNumber); 
	}

   private:
	std::map<std::string,std::string> contacts;
};

编译后g++报错:

In file included from D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/map:63,
                 from ./blog_code.cc:1:
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_map.h: In instantiation of 'void std::map<_Key, _Tp, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _InputIterator = std::__cxx11::basic_string<char>; _Key = std::__cxx11::basic_string<char>; _Tp = std::__cxx11::basic_string<char>; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >]':
./blog_code.cc:8:18:   required from here
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_map.h:944:38: error: no matching function for call to 'std::_Rb_tree<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >, std::_Select1st<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > > >::_M_insert_range_unique(std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&)'
  944 |         { _M_t._M_insert_range_unique(__first, __last); }
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
In file included from D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/map:62:
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h:1100:9: note: candidate: 'template<class _InputIterator> std::__enable_if_t<std::is_same<_Val, typename std::iterator_traits<_Iter>::value_type>::value> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_range_unique(_InputIterator, _InputIterator) [with _Key = std::__cxx11::basic_string<char>; _Val = std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; _KeyOfValue = std::_Select1st<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >]'
 1100 |         _M_insert_range_unique(_InputIterator __first, _InputIterator __last)
      |         ^~~~~~~~~~~~~~~~~~~~~~
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h:1100:9: note:   template argument deduction/substitution failed:
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h: In substitution of 'template<class _InputIterator> std::__enable_if_t<std::is_same<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >, typename std::iterator_traits< <template-parameter-1-1> >::value_type>::value, void> std::_Rb_tree<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >, std::_Select1st<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > > >::_M_insert_range_unique(_InputIterator, _InputIterator) [with _InputIterator = std::__cxx11::basic_string<char>]':
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_map.h:944:31:   required from 'void std::map<_Key, _Tp, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _InputIterator = std::__cxx11::basic_string<char>; _Key = std::__cxx11::basic_string<char>; _Tp = std::__cxx11::basic_string<char>; _Compare =
std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >]'
./blog_code.cc:8:18:   required from here
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h:1100:9: error: no type named 'value_type' in 'struct std::iterator_traits<std::__cxx11::basic_string<char> >'
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_map.h: In instantiation of 'void std::map<_Key, _Tp, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _InputIterator = std::__cxx11::basic_string<char>; _Key = std::__cxx11::basic_string<char>; _Tp = std::__cxx11::basic_string<char>; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >]':
./blog_code.cc:8:18:   required from here
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h:1109:9: note: candidate: 'template<class _InputIterator> std::__enable_if_t<(! std::is_same<_Val,
typename std::iterator_traits<_Iter>::value_type>::value)> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_range_unique(_InputIterator, _InputIterator) [with _Key = std::__cxx11::basic_string<char>; _Val = std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; _KeyOfValue = std::_Select1st<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >; _Compare = std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >]'
 1109 |         _M_insert_range_unique(_InputIterator __first, _InputIterator __last)
      |         ^~~~~~~~~~~~~~~~~~~~~~
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h:1109:9: note:   template argument deduction/substitution failed:
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h: In substitution of 'template<class _InputIterator> std::__enable_if_t<(! std::is_same<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >, typename std::iterator_traits< <template-parameter-1-1> >::value_type>::value), void> std::_Rb_tree<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >, std::_Select1st<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > > >::_M_insert_range_unique(_InputIterator, _InputIterator) [with _InputIterator = std::__cxx11::basic_string<char>]':
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_map.h:944:31:   required from 'void std::map<_Key, _Tp, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _InputIterator = std::__cxx11::basic_string<char>; _Key = std::__cxx11::basic_string<char>; _Tp = std::__cxx11::basic_string<char>; _Compare =
std::less<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >]'
./blog_code.cc:8:18:   required from here
D:/Scoop/apps/gcc/13.2.0/include/c++/13.2.0/bits/stl_tree.h:1108:59: error: no type named 'value_type' in 'struct std::iterator_traits<std::__cxx11::basic_string<char> >'
 1108 |         __enable_if_t<!__same_value_type<_InputIterator>::value>
      |

clang++报错:

In file included from .\blog_code.cc:1:
In file included from D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\include\map:12:
D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\include\xtree:1265:33: error: cannot increment value of type
      'std::basic_string<char>'
 1265 |         for (; _First != _Last; ++_First) {
      |                                 ^ ~~~~~~
D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\include\xtree:1274:9: note: in instantiation of function template
      specialization 'std::_Tree<std::_Tmap_traits<std::basic_string<char>, std::basic_string<char>, std::less<std::basic_string<char>>,
      std::allocator<std::pair<const std::basic_string<char>, std::basic_string<char>>>, false>>::_Insert_range_unchecked<std::basic_string<char>,
      std::basic_string<char>>' requested here
 1274 |         _Insert_range_unchecked(_STD _Get_unwrapped(_First), _STD _Get_unwrapped(_Last));
      |         ^
.\blog_code.cc:8:12: note: in instantiation of function template specialization 'std::_Tree<std::_Tmap_traits<std::basic_string<char>,
      std::basic_string<char>, std::less<std::basic_string<char>>, std::allocator<std::pair<const std::basic_string<char>, std::basic_string<char>>>,
      false>>::insert<std::basic_string<char>>' requested here
    8 |                 contacts.insert( name, phoneNumber );
      |                          ^
1 error generated.

错误分析

我们先抛开编译器莫名其妙的报错,来看看代码出了什么问题。

我们可以发现问题出在这里:

8		contacts.insert(name,phoneNumber); 

这里的contacts是一个std::map,而insert()方法期待接收参数std::pair<std::string,std::string>或两个迭代器_Iter _First,_Iter _Last,而实际传递的参数则是std::stringstd::string,所以导致了类型错误

修复

这个问题的修复很简单,只需要wrap一层std::pair即可使其传递std::pair<std::string,std::string>

8		contacts.insert(std::make_pair<std::string,std::string>(name,phoneNumber));

此外,还可以选择emplace()方法,这个方法可以接受一个pair作为参数,而不需要先创建一个pair再传递给insert方法。

8		contacts.emplace(name,phoneNumber); 

为什么报错这么混乱?

不论是g++还是报错相对友好的clang++,都产生了非常冗长并且混乱的报错。并且clangd没有检查出错误。

我们先从相对简单的clang++报错进行解析。

clang++的报错通常需要自底向上阅读。我们按照这个顺序开始。

./blog_code.cc:8:12: note: in instantiation of function template specialization 'std::_Hash<std::_Umap_traits<std::basic_string<char>,
      std::basic_string<char>, std::_Uhash_compare<std::basic_string<char>, std::hash<std::basic_string<char>>, std::equal_to<std::basic_string<char>>>,
      std::allocator<std::pair<const std::basic_string<char>, std::basic_string<char>>>, false>>::insert<std::basic_string<char>>' requested here
   8 |                 contacts.insert(name,phoneNumber);
      |                          ^
1 error generated.

这里的错误信息是说在contacts.insert(name,phoneNumber);这个语句中,clang++在编译时发现contacts是一个std::map,而insert方法期待接收参数std::pair<std::string,std::string>或两个迭代器_Iter _First,_Iter _Last,而实际传递的参数则是std::stringstd::string,所以导致了类型错误

随后,这个错误导致了STL一连串的报错:

D:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.43.34808/include/xhash:963:9: note: in instantiation of function template
      specialization 'std::_Hash<std::_Umap_traits<std::basic_string<char>, std::basic_string<char>, std::_Uhash_compare<std::basic_string<char>,
      std::hash<std::basic_string<char>>, std::equal_to<std::basic_string<char>>>, std::allocator<std::pair<const std::basic_string<char>,
      std::basic_string<char>>>, false>>::_Insert_range_unchecked<std::basic_string<char>, std::basic_string<char>>' requested here
  963 |         _Insert_range_unchecked(_STD _Get_unwrapped(_First), _STD _Get_unwrapped(_Last));
      |         ^

这里的错误信息是说在_Insert_range_unchecked这个函数中,clang++在编译时发现_First是一个std::string,而_Insert_range_unchecked方法期待接收参数_Iter _First,_Iter _Last,而实际传递的参数则是std::string,所以导致了类型错误

D:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.43.34808/include/xhash:954:33: error: cannot increment value of type
      'std::basic_string<char>'
  954 |         for (; _First != _Last; ++_First) {
      |                                 ^ ~~~~~~

这里的错误信息是说在for (; _First != _Last; ++_First)这个循环中,clang++在编译时发现_First是一个std::string,而_First期待接收参数_Iter _First,_Iter _Last,而实际传递的参数则是std::string,所以导致了类型错误

至此我们已经理清楚了clang++的报错内容。接下来看看g++的报错

g++的报错是从外到内逐层展开的,最外层的错误通常是问题的根源,而内层的错误是编译器在尝试解决问题时遇到的进一步问题。

在这个代码中:

  • 最外层是./blog_code.cc:8:18
  • 中间层是stl_map.hstl_tree.h,涉及STL的实现。
  • 最内层是模板推导失败,比如no type named 'value_type'

我们先从最外层开始:

./blog_code.cc:8:12: note: in instantiation of function template specialization 'std::_Hash<std::_Umap_traits<std::basic_string<char>,
      std::basic_string<char>, std::_Uhash_compare<std::basic_string<char>, std::hash<std::basic_string<char>>, std::equal_to<std::basic_string<char>>>,
      std::allocator<std::pair<const std::basic_string<char>, std::basic_string<char>>>, false>>::insert<std::basic_string<char>>' requested here
   8 |                 contacts.insert(name,phoneNumber);
      |                          ^

编译器尝试实例化std::mapinsert方法,但发现参数类型不匹配。

std::map::insert()有多个重载版本,但主要分为两类:

  1. 插入单个键值对

    • 接受一个std::pair<const Key, Value>对象。

    • 例如:

      contacts.insert(std::make_pair(name, phoneNumber));
      
  2. 插入一个范围

    • 接受两个迭代器_Iter _First_Iter _Last,表示一个范围。

    • 例如:

      contacts.insert(otherMap.begin(), otherMap.end());
      

而在代码中:

contacts.insert(name, phoneNumber); // 错误
  • 这个代码传递了两个std::string参数,而insert方法没有接受两个独立参数的版本。
  • 编译器将namephoneNumber解释为迭代器范围,但std::string不是迭代器类型,因此导致类型错误。
in instantiation of function template specialization 'std::_Hash<std::_Umap_traits<std::basic_string<char>, ...>>::insert<std::basic_string<char>>' requested here
  • 编译器尝试实例化std::mapinsert方法模板。但由于参数类型不匹配,模板实例化失败。
  • 具体来说,编译器期望insert的参数是std::pair或迭代器,而实际传递的参数是std::string.

杂谈

C++的报错真的是史无前例的混乱啊…………