浅墨浓香

想要天亮进城,就得天黑赶路。

导航

9.4 Decoding the Error Novel

9.4 破译大篇幅错误信息

 

Ordinary compilation errors are normally quite succinct and to the point. For example, when a compiler says “class X has no member ’fun’,” it usually isn’t too hard to figure out what is wrong in our code (e.g., we might have mistyped run as fun). Not so with templates. Let’s look at some examples.

普通的编译错误通常都是相当简洁的,并且能一针见血地指出问题所在。例如,当编译器给出“class X has no member ‘fun’”时,通常都能很快地找出代码中的错误(如我们把run写成了fun)。但是,模板的错误并不是这样的。让我们来看一些例子。

 

Simple Type Mismatch

简单类型不匹配的情况

 

Consider the following relatively simple example using the C++ standard library:

考虑下面这个使用了C++标准库的简单例子:

#include <string>
#include <map>
#include <algorithm>

int main()
{
    std::map<std::string,double> coll;
    …
    // 在col中找出第一个非空的字符串:
    auto pos = std::find_if (coll.begin(), coll.end(), 
                             [] (std::string const& s){
                                     return s != "";
                             });
}

It contains a fairly small mistake: In the lambda used to find the first matching string in the collection, we check against a given string. However, the elements in a map are key/value pairs, so that we should expect a std::pair<std::string const, double>.

其中有一处相当小的错误:使用lambda表达式在集合中查找第一个匹配的字符串,它依次将map中的元素和一个字符串比较。但是由于map元素是一个键/值对类型,因此期望传入lambda也应该是一个std::pair<std::string const, double>类型,而不是string类型。

 

A version of the popular GNU C++ compiler reports the following error:

现在,主流的GNU C++编译器会报如下错误:

 1 In file included from /cygdrive/p/gcc/gcc61-include/bits/stl_algobase.h:71:0,
 2 from /cygdrive/p/gcc/gcc61-include/bits/char_traits.h:39,
 3 from /cygdrive/p/gcc/gcc61-include/string:40,
 4 from errornovel1.cpp:1:
 5 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h: In instantiation of 'bool __gnu_cxx::__ops::_Iter_pred<_Predicate>::operator()(_Iterator) [with _Iterator = std::_Rb_tree_iterator<std::pair<const std::__cxx11::basic_string<char>, doubl::<lambda(const string&)>]':
 6 /cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:104:42: required from '_InputIterator std::__find_if(_InputIterator, _InputIterator, _Predicate, std::[with _InputIterator = std::_Rb_tree_iterator<std::pair<const s<char>, double> >; _Predicate = __gnu_cxx::__ops::_Iter_pred<<lambda(const string&)> >]'
 7 /cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:161:23: required from '_Iterator std::__find_if(_Iterator, _Iterator, _Predicate) [with _Iterator = std pair<const std::__cxx11::basic_string<char>, double> >; _Predic Iter_pred<main()::<lambda(const string&)> >]'
 8 /cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:3824:28: required from '_IIter std::find_if(_IIter, _IIter, _Predicate) [with _IIter = std::_Rb_tree_it std::__cxx11::basic_string<char>, double> >; _Predicate = main <lambda(const string&)>]'
 9 errornovel1.cpp:13:29: required from here
10 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11: error: no match for call to '(main()::<lambda(const string&)>) (std::pair<const std::__cxx11::basic_string<double>&)'
11 { return bool(_M_pred(*__it)); }
12 ^~~~~~~~~~~~~~~~~~~~
13 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11: note: candidate: bool (*)(const string&) {aka bool (*)(const std::__cxx11::basic_string<char>&)} <conversion>
14 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11: note: candidate expects 2 arguments, 2 provided
15 errornovel1.cpp:11:52: note: candidate: main()::<lambda(const string&)> 
16 [] (std::string const& s) {
17 ^
18 errornovel1.cpp:11:52: note: no known conversion for argument std::__cxx11::basic_string<char>, double>' to 'const string& {a basic_string<char>&}'
View Code

A message like this starts looking more like a novel than a diagnostic. It can also be overwhelming to the point of discouraging novice template users. However, with some practice, messages like this become manageable, and the errors are at least relatively easily located.

这样的信息看起来更像是一部小说,而不是诊断信息。它会大大打击模板初学者的信心。但是通过一些实践,类似这样的信息会是可应付的。至少,错误信息是相对容易定位的。

The first part of this error message says that an error occurred in a function template instance deep inside an internal predefined_ops.h header, included from errornovel1.cpp via various other headers. Here and in the following lines, the compiler reports what was instantiated with which arguments. In this case, it all started with the statement ending on line 13 of errornovel1.cpp, which is:

错误信息的第1部分表明:定义在内部predefined_ops.h头文件中的一个函数模板,在实例化过程中出现了一个错误。该头文件是在errornovel1.cpp中由多个其它头文件包含进来的。在这一行以及后面的几行中,编译器报告了哪些模板被哪些参数实例化。在这个例子中,所有的错误都从errornovel1.cpp的第13行结束的语句开始,该语句为:

auto pos = std::find_if (coll.begin(), coll.end(),
                         [] (std::string const& s) {
                             return s != "";
});

This caused the instantiation of a find_if template on line 115 of the stl_algo.h header, where the code

这导致stl_algo.h头文件中的第115行中的find_if模板实例化,其中的代码

_IIter std::find_if(_IIter, _IIter, _Predicate)

is instantiated with

会通过以下实例化

_IIter = std::_Rb_tree_iterator<std::pair<const std::__cxx11::basic_string<char>, double> >
_Predicate = main()::<lambda(const string&)>

The compiler reports all this in case we simply were not expecting all these templates to be instantiated. It allows us to determine the chain of events that caused the instantiations.

编译器报告了所有这些错误,我们可能并不希望看到所有被实例化的模板。但是这允许我们可以确定导致实例化的整个事件链。

However, in our example, we’re willing to believe that all kinds of templates needed to be instantiated, and we just wonder why it didn’t work. This information comes in the last part of the message: The part that says implies that a function call could not be resolved because the types of the arguments and the parameter types didn’t match. It lists what is called

然而,在这个例子中,我们相信所需要的模板都己经被实例化了,但还是不知道为什么还会出现错误。事实上,最后一部分信息给出了答案,它提到“no match for call”,这意味着一个函数调用的实参和形数类型不匹配,从而不能被解析。它列出了调用

 (main()::<lambda(const string&)>) (std::pair<const std::__cxx11::basic_string<char>, double>&)

and code that caused this call:

以及引起该调用的代码:

  { return bool(_M_pred(*__it)); }

Furthermore, just after this, the line containing “note: candidate:” explains that there was a single candidate type expecting a const string& and that this candidate is defined in line 11 of errornovel1.cpp as lambda [] (std::string const& s) combined with a reason why a possible candidate didn’t fit:

此外,紧随其后的是包含“note: candidate:”的行,它解释说是在errornovel1.cpp的第11行中定义候选类型lambda表达式,需要一个const string&类型的参数。并解释了这个候选类型不适合的原因:

1 no known conversion for argument 
2 from ’std::pair<const std::__cxx11::basic_string<char>, double>3 to ’const string& {aka const std::__cxx11::basic_string<char>&}’

which describes the problem we have.

它描述了我们所出现的真正问题。

 

There is no doubt that the error message could be better. The actual problem could be emitted before the history of the instantiation, and instead of using fully expanded template instantiation names like std::__cxx11::basic_string<char>, using just std::string might be enough. However, it is also true that all the information in this diagnostic could be useful in some situations. It is therefore not surprising that other compilers provide similar information (although some use the structuring techniques mentioned).

毫无疑问,错误消息可以做得更好。实际的问题可以在实例化之前,而不是完全展开模板实例化之前触发。例如,std::__cxx11::basic_string<char>这一名称,只需使用std::string就足够了。但是,这一诊断中所有信息在某些情况下也许是有用的。因此,其他编译器提供类似的信息也就不足为奇了(尽管有些编译器使用了前面提到过的结构化技术)。

For example, the Visual C++ compiler outputs something like:

例如,Visual C++编译器输出如下:

 1 c:\tools_root\cl\inc\algorithm(166): error C2664: 'bool main::<lambda_b863c1c7cd07048816f454330789acb4>::operator ()(const std::string &) const': cannot convert argument 1 from'std::pair<const _Kty,_Ty>' to 'const std::string &'
 2 with
 3 [
 4  _Kty=std::string,
 5  _Ty=double
 6 ]
 7 c:\tools_root\cl\inc\algorithm(166): note: Reason: cannot convert _Kty,_Ty>' to 'const std::string'
 8 with
 9 [
10  _Kty=std::string,
11  _Ty=double
12 ]
13 c:\tools_root\cl\inc\algorithm(166): note: No userdefined-conversion operator available that can perform this conversion, or the operator cannot be called
14 c:\tools_root\cl\inc\algorithm(177): note: see reference to function '_InIt std::_Find_if_unchecked<std::_Tree_unchecked_iterator<_(_InIt,_InIt,_Pr &)' being compiled
15 with
16 [
17  _InIt=std::_Tree_unchecked_iterator<std::_Tree_val<std::<std::pair<const std::string,double>>>>,
18  _Mytree=std::_Tree_val<std::_Tree_simple_types<std::pair double>>>,
19  _Pr=main::<lambda_b863c1c7cd07048816f454330789acb4>
20 ]
21 main.cpp(13): note: see reference to function template instantiation<std::_Tree_iterator<std::_Tree_val<std::_Tree_simple_types<std::p,main::<lambda_b863c1c7cd07048816f454330789acb4>>(_InIt,_InIt,_Pr)' being compiled
22 with
23 [
24  _InIt=std::_Tree_iterator<std::_Tree_val<std::_Tree_sim const std::string,double>>>>,
25  _Kty=std::string,
26  _Ty=double,
27  _Pr=main::<lambda_b863c1c7cd07048816f454330789acb4>
28 ]
View Code

Here, again, we provide the chain of instantiations with the information telling us what was instantiated by which arguments and where in the code, and we see twice that we

这里,再次为实例化链提供信息,该信息告诉我们实例化了哪些参数以及它们在代码中的位置。并且我们看到了两次

1 cannot convert from ’std::pair<const _Kty,_Ty>’ to ’const std::string2 with
3 [
4   _Kty=std::string,
5   _Ty=double
6 ]
View Code

Missing const on Some Compilers

在某些编译器上缺少const

 

Unfortunately, it sometimes happens that generic code is a problem only with some compilers. Consider the following example:

不幸的是,有时泛型代码只在某些编译器中会出现问题。考虑下面的例子:

#include <string>
#include <unordered_set>
class Customer
{
private:
    std::string name;
public:
    Customer(std::string const& n)
        : name(n) {
    }
    std::string getName() const {
        return name;
    }
};
int main()
{
    // provide our own hash function:
    struct MyCustomerHash {
        // NOTE: missing const is only an error with g++ and clang:
        std::size_t operator() (Customer const& c) {
            return std::hash<std::string>()(c.getName());
        }
    };
    // and use it for a hash table of Customers:std::unordered_set<Customer,MyCustomerHash> coll; …
}

With Visual Studio 2013 or 2015, this code compiles as expected. However, with g++ or clang, the code causes significant error messages. On g++ 6.1, for example, the first error message is as follows:

使用Visual Studio 2013或2015,这段代码将按预期的编译。但是,对于g++或者clang编译时,该代码会导致严重的错误消息。例如,在g++6.1上,第一条错误消息如下:

 1 In file included from /cygdrive/p/gcc/gcc61-include/bits/hashtable.h:35:0,
 2 from /cygdrive/p/gcc/gcc61-include/unordered_set:47,
 3 from errornovel2.cpp:2:
 4 /cygdrive/p/gcc/gcc61-include/bits/hashtable_policy.h: In instantiation of 'struct std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash>':
 5 /cygdrive/p/gcc/gcc61-include/type_traits:143:12:required from 'struct std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash> >'
 6 /cygdrive/p/gcc/gcc61-include/type_traits:154:38:required from 'struct std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash> > >'
 7 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63: required from 'class std::unordered_set<Customer, main()::MyCustomerHash>'
 8 errornovel2.cpp:28:47: required from here
 9 /cygdrive/p/gcc/gcc61-include/bits/hashtable_policy.h:85:34: error: no match for call to '(const main()::MyCustomerHash) (const Customer&)'
10 noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
11 ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
12 errornovel2.cpp:22:17: note: candidate: std::size_t main()::MyCustomerHash::operator()(const Customer&) <near match>
13 std::size_t operator() (const Customer& c) {
14 ^~~~~~~~
15 errornovel2.cpp:22:17: note: passing 'const main()::MyCustomerHash*' as 'this' argument discards qualifiers
View Code

immediately followed by more than 20 other error messages:

紧接着是其他的20多个错误消息:

 1 In file included from /cygdrive/p/gcc/gcc61-include/bits/move.h:57:0,
 2 from /cygdrive/p/gcc/gcc61-include/bits/stl_pair.h:59,
 3 from /cygdrive/p/gcc/gcc61-include/bits/stl_algobase.h:64,
 4 from /cygdrive/p/gcc/gcc61-include/bits/char_traits.h:39,
 5 from /cygdrive/p/gcc/gcc61-include/string:40,
 6 from errornovel2.cpp:1:
 7 /cygdrive/p/gcc/gcc61-include/type_traits: In instantiation of 'struct std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash> > >':
 8 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63: required from 'class
 9 std::unordered_set<Customer, main()::MyCustomerHash>'
10 errornovel2.cpp:28:47: required from here
11 /cygdrive/p/gcc/gcc61-include/type_traits:154:38: error: 'value' is not a member of 'std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash> >'
12 : public integral_constant<bool, !_Pp::value>
13 ^~~~
14 In file included from /cygdrive/p/gcc/gcc61-include/unordered_set:48:0,
15 from errornovel2.cpp:2:
16 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h: In instantiation of 'class std::unordered_set<Customer, main()::MyCustomerHash>':
17 errornovel2.cpp:28:47: required from here
18 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63: error: 'value' is not a member of 'std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash> >>'
19 typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc> _Hashtable;
20 ^~~~~~~~~~
21 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:102:45:error: 'value' is not a member of 'std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash> >>'
22 typedef typename _Hashtable::key_type key_type;
23 ^~~~~~~~
24
View Code

Again, it’s hard to read the error message (even finding the beginning and end of each message is a chore). The essence is that deep in header file hashtable_policy.h in the instantiation of std::unordered_set<> required by

同样,这是一段非常难懂的错误消息(甚至查找每个消息的开头和结尾也很麻烦)。其本质是std::unordered_set<>所需的头文件hashtable_policy.h的深层,需要

std::unordered_set<Customer,MyCustomerHash> coll;

there is no match for the call to

它不匹配如下调用

const main()::MyCustomerHash (const Customer&)

in the instantiation of

在下列的实例化中

noexcept(declval<const _Hash&>()(declval<const _Key&>()))>

~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~

(declval<const _Hash&>() is an expression of type main()::MyCustomerHash) .

(declval<const _Hash&>()是一个main()::MyCustomerHash类型的表达式)。

A possible “near match” candidate is

可能的“相似匹配”候选者是

std::size_t main()::MyCustomerHash::operator()(const Customer&)

which is declared as

它被声明为

std::size_t operator() (const Customer& c) {

^~~~~~~~

and the last note says something about the problem:

最后的注释说明了有关此问题所在:

passing ’const main()::MyCustomerHash*’ asthis’ argument discards qualifiers

Can you see what the problem is? This implementation of the std::unordered_set class template requires that the function call operator for the hash object be a const member function (see also Section 11.1.1 on page 159). When that’s not the case, an error arises deep in the guts of the algorithm.

看出问题了没有?std::unordered_set类模板的实现要求哈希对象的函数调用运算符是一个const成员函数(请参阅第159页的11.1.1节)。如果不是这样,就会在算法的内部产生一个错误。

All other error messages cascade from the first and go away when a const qualifier is simply added to the hash function operator:

所有的其他错误消息均是从第一个开始级联,当简单地在函数调用运算符后面加上const限定符时,所有的错误将消失:

std::size_t operator() (const Customer& c) const {
    … 
}

Clang 3.9 gives the slightly better hint at the end of the first error message that operator() of the hash functor is not marked const:

Clang 3.9在第一个错误消息的末尾给出了稍微更好一点的提示,即哈希对象的operator()未被标记为const:

 1  2 errornovel2.cpp:28:47: note: in instantiation of template class
 3 ’std::unordered_set<Customer
 4 , MyCustomerHash, std::equal_to<Customer>,
 5 std::allocator<Customer> >’ requested here
 6 std::unordered_set<Customer,MyCustomerHash> coll;
 7                                             ^
 8 errornovel2.cpp:22:17: note: candidate function not viable:
 9this’ argument has type ’const
10 MyCustomerHash’, but method is not marked const
11 std::size_t operator() (const Customer& c) {
12             ^
View Code

 

Note that clang here mentions default template parameters such as std::allocator<Customer>, while gcc skips them.

注意,clang在这里提到了默认模板参数,比如std::allocator<Customer>,但是gcc则跳过它们。

 

As you can see, it is often helpful to have more than one compiler available to test your code. Not only does it help you write more portable code, but where one compiler produces a particularly inscrutable error message, another might provide more insight.

如您所见,拥有多个可用的编译器来测试代码通常是有帮助的。它不仅帮助您编写更可移植的代码,而且当一个编译器产生一个特别难以理解的错误消息时,另一个编译器可能会提供更友好的见解。