Messages d’erreurs des compilateurs C++ : étude comparative

Clang, le compilateur C/C++ présenté dans cet article, est essentiellement compatible à gcc (à part quelques extensions GNU) et supporte quasiment l’intégralité du standard C++11. L’un des avantages pour le développeur est le diagnostique de code plus utile. C’est le point qui nous intéresse dans cet article.

Les diagnostiques correspondent aux messages d’erreurs ou d’avertissements des compilateurs. Leur qualité a une influence non négligeable sur les temps de développement et de débogage, même pour des développeurs expérimentés.

Nous comparons ici des versions récentes de quatre compilateurs:

  • Clang 3.2 – communauté LLVM,
  • GCC 4.7 – communauté GNU,
  • ICC 13.1 – Intel,
  • Visual C++ 2012 – Microsoft.

Les résultats ci-dessous illustrent le résultat de l’étude. Ces résultats sont expliqués dans la suite.

clang
15.5
gcc
5.0
icc
10.5
visual
5.0

Etant donné la complexité du langage C++, lorsqu’un code ne compile pas il n’est pas toujours évident de comprendre l’origine du problème à partir des messages d’erreurs. Le problème est d’autant plus gênant pour les développeurs peu expérimentés et sur les projets importants ou utilisant beaucoup la programmation générique. Une étude comparative des diagnostiques des différents compilateurs est menée ici.

La méthodologie est la suivante: 6 code sont donnés aux 4 compilateurs, les messages d’erreurs sont reproduits dans l’article et classés du plus utile au moins utile. L’utilité s’entend vis-à-vis du temps que mettera le développeur à identifier, comprendre et corriger le problème dans son code. Le compilateur le plus utile gagne 3 points, le deuxième 2 points, etc. En cas d’ex-aequo la somme des points d’un code est toujours égale à 6 et un compilateur ne peux gagner plus de 3 points. Cette évaluation revet forcément un caractère subjectif, mais les choix sont argumentés et toutes les données sont reproduites dans l’article permettant au lecteur de tirer ses propres conclusions.

1. Erreur de points-virgules

Commençons par un exemple simple: trois classes sont définies avec un point-virgule manquant ou en trop pour chacune.

Le code

Un point-virgule a été omis ou rajouté dans chacune des classe:

 1 class A {
 2 	int x;
 3 } // ;
 4 
 5 class B {
 6 	int x // ;
 7 };
 8 
 9 class C; /* ; en trop */ {
10 	int x;
11 };
12 

Diagnostic de clang

Explications très claires, sauf pour la dernière erreur. La réécriture du code erroné et l’identification de l’endroit (caractère) exact de l’erreur rendent l’interprétation facile, même pour la dernière erreur où le message est semblable à gcc. La colorisation de la sortie rend bien plus rapide la lecture et l’interprétation du diagnostic. On note également que clang détecte le fait que le champ x semble inutile.

../semicolon.cpp:3:2: error: expected ';' after class
} // ;
 ^
 ;
../semicolon.cpp:6:7: error: expected ';' at end of declaration list
        int x // ;
             ^
             ;
../semicolon.cpp:9:26: error: expected unqualified-id
class C; /* ; en trop */ {
                         ^
../semicolon.cpp:2:6: warning: private field 'x' is not used [-Wunused-private-field]
        int x;
            ^
../semicolon.cpp:6:6: warning: private field 'x' is not used [-Wunused-private-field]
        int x // ;
            ^
2 warnings and 3 errors generated.

Diagnostic de gcc

compact et clair, sauf pour la dernière erreur:

../semicolon.cpp:3:1: error: expected ‘;’ after class definition
../semicolon.cpp:6:6: error: expected ‘;’ at end of member declaration
../semicolon.cpp:9:26: error: expected unqualified-id before ‘{’ token

Diagnostic de icc

Comme dans clang des indicateurs de position sont visibles, et les messages d’erreur sont clairs. Par contre, comme pour gcc l’absence de couleur rend la lecture un peu plus pénible.

../semicolon.cpp(5): error: expected a ";"
  class B {
  ^

../semicolon.cpp(7): error #65: expected a ";"
  };
  ^

../semicolon.cpp(9): error: expected a declaration
  class C; /* ; en trop */ {
                           ^

compilation aborted for ../semicolon.cpp (code 2)

Diagnostic de visual

Visual ne diagnostique pas la deuxième erreur (ligne 6). Pour le reste, les messages d’erreur sont peu compréhensibles, et seule la permière ligne mentionne l’absense de point-virgule.

1>vs-test.cpp(5): error C2236: unexpected token 'class'. Did you forget a ';'?
1>vs-test.cpp(5): error C2143: syntax error : missing ';' before '{'
1>vs-test.cpp(5): error C2447: '{' : missing function header (old-style formal list?)
1>vs-test.cpp(9): error C2447: '{' : missing function header (old-style formal list?)

Verdict

  1. clang: messages clairs, colorés et facile à lire, indicateurs de positions précis. Avertissements de champ inutilisé.
  2. icc: messages clairs et présence d’indicateurs de position.
  3. gcc: messages compacts.
  4. visual: il manque une erreur, les messages sont inutilement longs et peu compréhensibles.
clang
3
gcc
1
icc
2
visual
0

2. Variables non initialisées

On s’intéresse ici à la sensibilité des compilateurs à l’utilisation de variables non initialisées. En effet, ce type d’utilisation est dangereux et peut provoquer des bugs subtiles. Une détection par le compilateur peut donc faire gagner du temps.

Le code

Le code ci-dessous présente deux cas d’utilisation de variable non initialisée. La variable v dans la fonction main n’est jamais initialisée, et la variable value de la fonction f n’est initialisée que dans certains cas (si argc < 2).

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 int f(bool flag) {
 5 	int value;
 6 	if(flag) value = 7;
 7  	return (value == 7);
 8 }
 9 
10 int main(int argc, char *argv[]) {
11 	bool v;
12 	return f(argc < 2) && v;
13 }

Tous les compilateurs détectent l’utilisation de v, mais seul clang détecte l’utilisation de value qui est problématique dans certains cas.

Diagnostic de clang

le compilateur basé sur LLVM détecte deux problèmes: la variable v non initialisée dans le main, et la variable value qui n’est initialisée que dans certains cas. Par contre, les messages sont peut être un peu trop verbeux.

../uninitialized.cpp:6:5: warning: variable 'value' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized]
        if(flag) value = 7;
           ^~~~
../uninitialized.cpp:7:11: note: uninitialized use occurs here
        return (value == 7);
                ^~~~~
../uninitialized.cpp:6:2: note: remove the 'if' if its condition is always true
        if(flag) value = 7;
        ^~~~~~~~~
../uninitialized.cpp:5:11: note: initialize the variable 'value' to silence this warning
        int value;
                 ^
                  = 0
../uninitialized.cpp:12:24: warning: variable 'v' is uninitialized when used here [-Wuninitialized]
        return f(argc < 2) && v;
                              ^
../uninitialized.cpp:11:8: note: initialize the variable 'v' to silence this warning
        bool v;
              ^
               = false
2 warnings generated.

Diagnostic de gcc

Seule la variable v est détectée par gcc, pas la variable value qui est pourtant non-initialisée dans certains cas.

../uninitialized.cpp: In function ‘int main(int, char**)’:
../uninitialized.cpp:12:24: warning: ‘v’ may be used uninitialized in this function [-Wuninitialized]

Diagnostic de icc

ICC donne un message très clair sans être inutilement long, mais ne détecte pas la variable value.

../uninitialized.cpp(12): warning #592: variable "v" is used before its value is set
  	return f(argc < 2) && v;
  	                      ^

Diagnostic de visual

Le message de visual est consis et clair, mais ne détecte pas la variable value. Par contre, la sortie de visual n’est pas colorée et la position de l’erreur est peu précise (juste un numéro de ligne).

1>vs-test.cpp(12): warning C4700: uninitialized local variable 'v' used

Verdict

  • clang: détecte toutes les variables non initialisées.
  • icc, gcc et visual n’avertissent pas lorsque la variable n’est initialisée que dans certaines branches.
clang
3
gcc
1
icc
1
visual
1

3. Affichage via cout

Cet exemple présente un grand classique du C++: l’utilisation de la sortie standard cout avec un type pour lequel l’opérateur << adéquat n’est pas définit.

Le code

Il n’existe pas d’opérateur << prenant des arguments de type ostream (cout) et A. Autrement dit, C++ ne sait pas afficher un objet de type A.

 1 #include <iostream>
 2 using namespace std;
 3 
 4 struct A {};
 5 
 6 int main() {
 7 	cout << "A = " << A();
 8 	return 0;
 9 }

Diagnostic de clang

Le message d’erreur (première ligne) est clair, d’autant plus que l’opérateur problématique est mis en évidence. Par contre, clang donne également la liste exhaustive des surcharges de l’opérateur << qu’il a examiné et explique pourquoi elles ne conviennent pas. Dans d’autres cas, cette information peut être intéressante, mais ici elle ne fait que rajouter beaucoup de bruit parasite.

../print.cpp:7:17: error: invalid operands to binary expression ('basic_ostream<char, std::char_traits<char> >' and 'A')
        cout << "A = " << A();
        ~~~~~~~~~~~~~~ ^  ~~~
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:105:7: note: candidate function not viable: no known conversion from 'A' to '__ostream_type &(*)(__ostream_type &)' for 1st argument
      operator<<(__ostream_type& (*__pf)(__ostream_type&))
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:114:7: note: candidate function not viable: no known conversion from 'A' to '__ios_type &(*)(__ios_type &)' for 1st argument
      operator<<(__ios_type& (*__pf)(__ios_type&))
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:124:7: note: candidate function not viable: no known conversion from 'A' to 'std::ios_base &(*)(std::ios_base &)' for 1st argument
      operator<<(ios_base& (*__pf) (ios_base&))
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:163:7: note: candidate function not viable: no known conversion from 'A' to 'long' for 1st argument
      operator<<(long __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:167:7: note: candidate function not viable: no known conversion from 'A' to 'unsigned long' for 1st argument
      operator<<(unsigned long __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:171:7: note: candidate function not viable: no known conversion from 'A' to 'bool' for 1st argument
      operator<<(bool __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:175:7: note: candidate function not viable: no known conversion from 'A' to 'short' for 1st argument
      operator<<(short __n);
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:178:7: note: candidate function not viable: no known conversion from 'A' to 'unsigned short' for 1st argument
      operator<<(unsigned short __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:186:7: note: candidate function not viable: no known conversion from 'A' to 'int' for 1st argument
      operator<<(int __n);
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:189:7: note: candidate function not viable: no known conversion from 'A' to 'unsigned int' for 1st argument
      operator<<(unsigned int __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:198:7: note: candidate function not viable: no known conversion from 'A' to 'long long' for 1st argument
      operator<<(long long __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:202:7: note: candidate function not viable: no known conversion from 'A' to 'unsigned long long' for 1st argument
      operator<<(unsigned long long __n)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:217:7: note: candidate function not viable: no known conversion from 'A' to 'double' for 1st argument
      operator<<(double __f)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:221:7: note: candidate function not viable: no known conversion from 'A' to 'float' for 1st argument
      operator<<(float __f)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:229:7: note: candidate function not viable: no known conversion from 'A' to 'long double' for 1st argument
      operator<<(long double __f)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:242:7: note: candidate function not viable: no known conversion from 'A' to 'const void *' for 1st argument
      operator<<(const void* __p)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:267:7: note: candidate function not viable: no known conversion from 'A' to '__streambuf_type *' (aka 'basic_streambuf<char, std::char_traits<char> > *') for 1st argument
      operator<<(__streambuf_type* __sb);
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:473:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'char' for 2nd argument
    operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:479:5: note: candidate function [with _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'char' for 2nd argument
    operator<<(basic_ostream<char, _Traits>& __out, char __c)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:485:5: note: candidate function [with _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'signed char' for 2nd argument
    operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:490:5: note: candidate function [with _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'unsigned char' for 2nd argument
    operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:527:5: note: candidate function [with _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'const char *' for 2nd argument
    operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:540:5: note: candidate function [with _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'const signed char *' for 2nd argument
    operator<<(basic_ostream<char, _Traits>& __out, const signed char* __s)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:545:5: note: candidate function [with _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'const unsigned char *' for 2nd argument
    operator<<(basic_ostream<char, _Traits>& __out, const unsigned char* __s)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:599:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>, _Tp = A] not viable: no known conversion from 'basic_ostream<char, std::char_traits<char> >' to 'basic_ostream<char, std::char_traits<char> > &&' for 1st argument
    operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/bits/ostream.tcc:322:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>] not viable: no known conversion from 'A' to 'const char *' for 2nd argument
    operator<<(basic_ostream<_CharT, _Traits>& __out, const char* __s)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:468:5: note: candidate template ignored: deduced conflicting types for parameter '_CharT' ('char' vs. 'A')
    operator<<(basic_ostream<_CharT, _Traits>& __out, _CharT __c)
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/bits/basic_string.h:2749:5: note: candidate template ignored: failed template argument deduction
    operator<<(basic_ostream<_CharT, _Traits>& __os,
    ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:510:5: note: candidate template ignored: failed template argument deduction
    operator<<(basic_ostream<_CharT, _Traits>& __out, const _CharT* __s)
    ^
1 error generated.

Diagnostic de gcc

Le message de gcc est court mais incompréhensible. D’après gcc le problème semble venir de références incompatibles sur cout et non de l’utilisation de a.

../print.cpp: In function ‘int main()’:
../print.cpp:7:22: error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’
In file included from /usr/include/c++/4.7/iostream:40:0,
                 from ../print.cpp:1:
/usr/include/c++/4.7/ostream:600:5: error:   initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = A]’

Diagnostic de icc

ICC sort un message d’erreur à la fois court, précis et compréhensible.

../print.cpp(7): error: no operator "<<" matches these operands
            operand types are: std::basic_ostream<char, std::char_traits<char>> << A
  	cout << "A = " << A();
  	               ^

compilation aborted for ../print.cpp (code 2)

Diagnostic de visual

Le compilateur de Microsoft donne un message d’erreur clair mais détaille toutes les conversions envisagées, et remporte ainsi la palme du diagnostique le plus long.

1>vs-test.cpp(7): error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'A' (or there is no acceptable conversion)
1>          d:\visual-2012-express\vc\include\ostream(695): could be 'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(742): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,char)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(780): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(827): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,char)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(953): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const signed char *)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(960): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,signed char)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(967): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const unsigned char *)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(974): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,unsigned char)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(984): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>,A>(std::basic_ostream<_Elem,_Traits> &&,const _Ty &)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>,
1>              _Ty=A
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(1101): or       'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const std::error_code &)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(201): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(std::basic_ostream<_Elem,_Traits> &(__cdecl *)(std::basic_ostream<_Elem,_Traits> &))'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(207): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(std::basic_ios<_Elem,_Traits> &(__cdecl *)(std::basic_ios<_Elem,_Traits> &))'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(214): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(std::ios_base &(__cdecl *)(std::ios_base &))'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(221): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(std::_Bool)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(241): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(short)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(275): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(unsigned short)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(295): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(int)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(320): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(unsigned int)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(340): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(long)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(360): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(unsigned long)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(381): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(__int64)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(401): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(unsigned __int64)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(422): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(float)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(442): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(double)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(462): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(long double)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(482): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(const void *)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\visual-2012-express\vc\include\ostream(502): or       'std::basic_ostream<_Elem,_Traits> &std::basic_ostream<_Elem,_Traits>::operator <<(std::basic_streambuf<_Elem,_Traits> *)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          while trying to match the argument list '(std::basic_ostream<_Elem,_Traits>, A)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]

Verdict

  • icc: très clair et concis.
  • clang & visual: message clair mais inutilement long (pas gênant dans un IDE qui filtre les messages).
  • gcc: message court mais mal diagnostiqué.
clang
1.5
gcc
0
icc
3
visual
1.5

4. Multiplication impossible

Ici on s’intéresse à des types produits par des techniques de méta-programmation, et une multiplication impossible entre des objets de ces types. Les compilateurs arrivent plus ou moins bien à expliciter l’origine du problème.

Le code

On définit ici un type conditionnel transform<T> qui vaut T si c’est un flottant (float ou double), et le type nothing dans les autres cas; la fonction tr permet d’appliquer ce principe à des valeurs. Dans le main(), on multiplie tr(1) (une instance de nothing) et tr(1.0) (le double de valeur 1). Aucun opérateur de multiplication n’est définit pour ces types d’où l’erreur.

 1 #include <type_traits>
 2 
 3 // classe vide, avec un constructeur prenant un argument quelconque
 4 struct nothing {
 5 	template<class T> nothing(T) {}
 6 };
 7 
 8 // transform<T> = is_floating_point<T> ? T : nothing
 9 template<class T> using transform = typename std::conditional<
10 	std::is_floating_point<T>::value,
11 	T, nothing
12 >::type;
13 
14 // tr(x) = is_floating_point(x) ? x : nothing
15 template<class X> transform<X> tr(X x) { return x; }
16 
17 int main() { return tr(1) * tr(1.0); }

Tous les compilateurs identifient le problème, mais l’explication est plus ou moins claire.

Diagnostic de clang

Le compilateur de la suite LLVM explicite la transformation de type qui a lieu ce qui permet immédiatement de comprendre pourquoi la multiplication est impossible.

../condtype.cpp:17:27: error: invalid operands to binary expression ('transform<int>' (aka 'nothing') and 'transform<double>' (aka 'double'))
int main() { return tr(1) * tr(1.0); }
                    ~~~~~ ^ ~~~~~~~
1 error generated.

Diagnostic de gcc

GCC se contente de recopier le code, ce qui demande plus de travail au développeur pour comprendre le code et l’origine du problème.

../condtype.cpp: In function ‘int main()’:
../condtype.cpp:17:35: error: no match for ‘operator*’ in ‘tr<int>(1) * tr<double>(1.0e+0)’

Diagnostic de icc

Le message d’icc montre bien que le problème vient de la multiplication mais n’explicite pas les types.

../condtype.cpp(17): error: no operator "*" matches these operands
            operand types are: transform<int> * transform<double>
  int main() { return tr(1) * tr(1.0); }
                            ^

compilation aborted for ../condtype.cpp (code 2)

Diagnostic de visual

Les messages de visual sont peu clairs, et n’identifient pas le vrai problème. En fait Visual C++ 2012 ne supporte pas encore la syntaxe « using type = … ».

1>vs-test.cpp(9): error C2988: unrecognizable template declaration/definition
1>vs-test.cpp(9): error C2059: syntax error : 'using'
1>vs-test.cpp(15): error C2143: syntax error : missing ';' before '{'
1>vs-test.cpp(15): error C2447: '{' : missing function header (old-style formal list?)
1>vs-test.cpp(17): error C3861: 'tr': identifier not found

Verdict

  • clang: explicite clairement les types intervenant dans la multiplication.
  • gcc & icc: n’explicitent pas les types, le développeur doit donc faire ce travail.
  • visual: indique de manière peu claire un problème différent dû au support incomplet de C++11.
clang
3
gcc
1.5
icc
1.5
visual
0

5. Instanciation récursive de templates

L’exemple suivant met en évidence la difficulté de débogage des codes faisant intervenir des techniques de méta-programmation, dans ce cas un template récursif.

Le code

La classe S est un template récursif avec un point d’arrêt en <0>, et un pattern où S<n> dépend de S<n-1>. S est un foncteur (il définit un opérateur call prenant un argument quelconque et retournant un entier). Une incohérence s’est glissée dans la règle de récursion: le foncteur est appelé sans argument dans ‘f()’. Additionneraient, dans le main, le namespace std a été omis dans l’utilisation de endl.

 1 #include <iostream>
 2 #include <type_traits>
 3 template<int n> struct S{
 4 	S<n-1> p;
 5 	template<class T> int operator()(T& f) const { return f() + p(f); }
 6 };
 7 template<> struct S<0>{
 8 	template<class T> int operator()(T& f) const { return f(0); }
 9 };
10 
11 int main() {
12    S<10> f;
13    /* f(f) ne peut pas compiler car dans f(g),
14     * g doit pouvoir être appelé sans argument */
15    std::cout << f(f) << /* manque std */endl;
16    return 0;
17 }
18 

Sur ce type de code, les compilateurs vont avoir tendance à massivement multiplier les messages d’erreurs en examinant chaque instanciation de template séparément. Pour cet exemple, clang arrive à comprendre le pattern et à ne présenter qu’un seul message d’erreur.

Diagnostic de clang

Clang identifie correctement les deux problèmes: l’utilisation de endl sans std, et l’utilisation de f() sans arguments alors qu’il en faut un.

../recursion.cpp:15:41: error: use of undeclared identifier 'endl'; did you mean 'std::endl'?
   std::cout << f(f) << /* manque std */endl;
                                        ^~~~
                                        std::endl
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/ostream:561:5: note: 'std::endl' declared here
    endl(basic_ostream<_CharT, _Traits>& __os)
    ^
../recursion.cpp:5:56: error: no matching function for call to object of type 'S<10>'
        template<class T> int operator()(T& f) const { return f() + p(f); }
                                                              ^
../recursion.cpp:15:18: note: in instantiation of function template specialization 'S<10>::operator()<S<10> >' requested here
   std::cout << f(f) << /* manque std */endl;
                 ^
../recursion.cpp:5:24: note: candidate function template not viable: requires single argument 'f', but no arguments were provided
        template<class T> int operator()(T& f) const { return f() + p(f); }
                              ^
2 errors generated.

Diagnostic de gcc

gcc identifie std::endl mais de manière moins lisible que clang. Par contre, l’appel de f() n’est pas clairement identifié, et à la place une pile d’erreur correspondant à chaque niveau de récursion est produite.

../recursion.cpp: In function ‘int main()’:
../recursion.cpp:15:41: error: ‘endl’ was not declared in this scope
../recursion.cpp:15:41: note: suggested alternative:
In file included from /usr/include/c++/4.7/iostream:40:0,
                 from ../recursion.cpp:1:
/usr/include/c++/4.7/ostream:562:5: note:   ‘std::endl’
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’:
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’:
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 8]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 7]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 6]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 5]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 4]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 3]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 2]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 1]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:5:65: error: no match for call to ‘(S<10>) ()’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: template<class T> int S::operator()(T&) const [with T = T; int n = 10]
../recursion.cpp:5:24: note:   template argument deduction/substitution failed:
../recursion.cpp:5:65: note:   candidate expects 1 argument, 0 provided
../recursion.cpp: In instantiation of ‘int S<0>::operator()(T&) const [with T = S<10>]’:
../recursion.cpp:5:65:   recursively required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 9]’
../recursion.cpp:5:65:   required from ‘int S<n>::operator()(T&) const [with T = S<10>; int n = 10]’
../recursion.cpp:15:20:   required from here
../recursion.cpp:8:59: error: no match for call to ‘(S<10>) (int)’
../recursion.cpp:3:24: note: candidate is:
../recursion.cpp:5:24: note: int S<n>::operator()(T&) const [with T = int; int n = 10]
../recursion.cpp:5:24: note:   no known conversion for argument 1 from ‘int’ to ‘int&’

Diagnostic de icc

Le compilateur d’intel ne suggère pas l’ajout de std:: mais se contente d’indiquer que endl n’est pas définit. Pour l’appel à f(), le message d’erreur est relativement compréhensible mais répété pour chaque instanciation du template, ce qui produit un diagnostic étonnamment long.

../recursion.cpp(15): error: identifier "endl" is undefined
     std::cout << f(f) << /* manque std */endl;
                                          ^

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=5, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=4, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=5, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=3, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=4, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=5, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=2, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=3, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=4, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=5, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(5): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f() + p(f); }
  	                                                      ^
          detected during:
            instantiation of "int S<n>::operator()(T &) const [with n=1, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=2, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=3, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=4, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=5, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

../recursion.cpp(8): error: no instance of function template "S<n>::operator() [with n=10]" matches the argument list
            argument types are: (int)
            object type is: S<10>
  	template<class T> int operator()(T& f) const { return f(0); }
  	                                                      ^
          detected during:
            instantiation of "int S<0>::operator()(T &) const [with T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=1, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=2, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=3, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=4, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=5, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=6, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=7, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=8, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=9, T=S<10>]" at line 5
            instantiation of "int S<n>::operator()(T &) const [with n=10, T=S<10>]" at line 15

compilation aborted for ../recursion.cpp (code 2)

Diagnostic de visual

Le compilateur de Microsoft détecte les erreurs en plusieurs passes: sur le code donné il n’indique que le problème lié à endl (sans suggestion de std), et ce n’est qu’une fois ce problème corrigé qu’il détecte le problème lié aux templates. Par contre il ne duplique pas l’erreur pour chaque instanciation et le diagnostique est clair.

1>vs-test.cpp(15): error C2065: 'endl' : undeclared identifier

1>vs-test.cpp(5): error C2780: 'int S<n>::operator ()(T &) const' : expects 1 arguments - 0 provided
1>          with
1>          [
1>              n=10
1>          ]
1>          vs-test.cpp(5) : see declaration of 'S<n>::operator ()'
1>          with
1>          [
1>              n=10
1>          ]
1>          vs-test.cpp(15) : see reference to function template instantiation 'int S<n>::operator ()<S<n>>(T &) const' being compiled
1>          with
1>          [
1>              n=10,
1>              T=S<10>
1>          ]
1>          vs-test.cpp(15) : see reference to function template instantiation 'int S<n>::operator ()<S<n>>(T &) const' being compiled
1>          with
1>          [
1>              n=10,
1>              T=S<10>
1>          ]

Verdict

  • clang & visual: 2 messages d’erreur compréhensibles et bien diagnostiqués.
  • gcc & icc: répétition des méssage pour chaque instanciation, ce qui rend la lecture bien plus difficile!
clang
2
gcc
1
icc
1
visual
2

6. Typename et std manquants

Dans l’exemple précédant la qualité des diagnostiques avait peu d’importance car le problème (point-virgule manquant ou en trop) est facile à diagnostiquer, même pour un développeur junior. Voyons maintenant un exemple plus réaliste: l’oubli de préfixer par ‘std::’ un symbole de la stl, et l’omission par oubli ou méconnaissance du préfixe typename pour les types dépendant de templates (voir ci-dessous pour l’explication du problème).

Le code

La classe ‘strip’ ci-dessous peut être vue comme une méta-fonction qui prend un type en argument (T) et renvoie un type (type). Sa sémantique est de supprimer les références et la qualification const d’un type: par exemple const int& deviendra int, c’est-à-dire strip::type est le type int.
Le code contient deux erreurs. La première est l’oubli de std:: devant remove_const qui appartient au namespace std. La deuxième est l’absence de typename devant std::remove_reference::type. Explications juste en dessous du code.

 1 #include <type_traits>
 2 
 3 template<class T>
 4 struct strip:
 5 	remove_const<std::remove_reference<T>::type>
 6 {};
 7 
 8 int main() {
 9 	return strip<const int&>::type(0);
10 }

Pourquoi faudrait-il un typename?
Explications: nous sommes à l’intérieur d’un template d’argument T, et l’expression remove_reference::type dépends de T, or à la compilation du template, T est une variable sans valeur concrète. Par conséquent, remove_reference::type est également une expression abstraite, et le compilateur ne sait pas ce qu’est ‘type’: une valeur, un type, une fonction? Pour comprendre la difficulté, il convient de rappeler que les templates peuvent être spécialisés, et que même si la STL définit remove_reference::type comme un type, n’importe qui peut spécialiser à tout moment remove_reference, et par exemple pour T=int définir type comme un champ de type bool et pas comme un typedef. Bref, il serait trop coûteux d’analyser toutes les spécialisations possibles, et pour cette raison, les expressions dépendant d’argument de template sont purement symboliques: le compilateur ne sait pas s’il s’agit d’un type ou d’une valeur. Pour pouvoir quand même évaluer la validité du code, le compilateur considère par défaut que toute expression dépendante d’arguments de template est une valeur (de type inconnu). S’il s’agit d’un type, il faut l’indiquer avec le préfixe typename.

Diagnostic de clang

L’oubli du qualificateur de namespace std:: est clairement identifiée par clang. La coloration et la réécriture de code ont ici une forte valeur ajoutée. De même, le compilateur suggère de rajouter typename. La dernière erreur intervient dans main() et est due aux précédentes.

../typename.cpp:5:2: error: no template named 'remove_const'; did you mean 'std::remove_const'?
        remove_const<std::remove_reference<T>::type>
        ^~~~~~~~~~~~
        std::remove_const
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/type_traits:1278:12: note: 'std::remove_const' declared here
    struct remove_const
           ^
../typename.cpp:5:15: error: template argument for template type parameter must be a type; did you forget 'typename'?
        remove_const<std::remove_reference<T>::type>
                     ^
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/type_traits:1277:21: note: template parameter is declared here
  template<typename _Tp>
                    ^
../typename.cpp:9:28: error: no member named 'type' in 'strip<const int &>'
        return strip<const int&>::type(0);
               ~~~~~~~~~~~~~~~~~~~^
3 errors generated.

Diagnostic de gcc

Le compilateur gcc provoque 4 erreurs, mais aucune d’entre elles ne donnent d’indice quand à l’origine du problème.

../typename.cpp:5:14: error: expected template-name before ‘<’ token
../typename.cpp:5:14: error: expected ‘{’ before ‘<’ token
../typename.cpp:5:14: error: expected unqualified-id before ‘<’ token
../typename.cpp: In function ‘int main()’:
../typename.cpp:9:9: error: incomplete type ‘strip<const int&>’ used in nested name specifier

Diagnostic de icc

Le compilateur d’intel indique la position du problème de manière très imprécise, et les messages d’erreur ne sont pas très clairs.

../typename.cpp(5): error: remove_const is not a template
  	remove_const<std::remove_reference<T>::type>
  	^

../typename.cpp(5): error: not a class or struct name
  	remove_const<std::remove_reference<T>::type>
  	^

../typename.cpp(9): error: class "strip<const int &>" has no member "type"
  	return strip<const int&>::type(0);
  	                          ^

compilation aborted for ../typename.cpp (code 2)

Diagnostic de visual

La seule information utile est le numéro de ligne des erreurs, car les messages apportent très peu d’information sur le vrai problème.

1>vs-test.cpp(5): error C2504: 'remove_const' : base class undefined
1>          vs-test.cpp(9) : see reference to class template instantiation 'strip<T>' being compiled
1>          with
1>          [
1>              T=const int &
1>          ]
1>vs-test.cpp(9): error C2039: 'type' : is not a member of 'strip<T>'
1>          with
1>          [
1>              T=const int &
1>          ]
1>vs-test.cpp(9): error C3861: 'type': identifier not found

Verdict

  • clang: messages utiles et proposition du correctif.
  • icc: position de l’erreur peu précise et messages d’erreurs difficiles à interpréter.
  • gcc & visual: la seule information utile est le numéro de ligne.
clang
3
gcc
0.5
icc
2
visual
0.5

7. Conclusion

Dans les 6 études menés, le compilateur clang de la suite LLVM donne des messages de diagnostics de meilleure qualité sauf dans un cas (affichage impossible via cout << où le message est inutilement long). Clang est donc le vainqueur en terme de qualité des diagnostics. Il est intéressant de constater qu’il est capable de donner des avertissements plus riches, par exemple dans le cas d’utilisation de variables non initialisées.Le compilateur d’Intel arrive en deuxième position avec des messages souvent plus faciles à comprendre que ceux de GCC et Visual qui partagent la dernière position.La conclusion à tirer de cette étude n’est surtout pas d’utiliser systématiquement Clang. La variabilité des résultats selon les codes ainsi qu’une longue expérience montrent qu’il est toujours bon d’utiliser plusieurs compilateurs. Un bon workflow doit permettre de passer rapidement d’un compilateur à l’autre. Ainsi quand le compilateur par défaut du projet donne des diagnostiques difficiles à interpréter, un autre pourra donner un message plus clair. De plus, comme le démontre le cas des variables non initialisées, l’utilisation de plusieurs compilateurs permet de réaliser une analyse statique plus exhaustive du code et ainsi d’éviter des bugs au runtime en les détectant dès la compilation.Dans un article suivant, nous présenterons une étude sur le temps de compilation des différents compilateur. Sur cet aspect, Clang se proclame compilateur le plus rapide – c’est ce que nous vérifierons expérimentalement.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *