Comparison of Diagnostics between GCC and Clang

It is often repeated that the Clang compiler produces far superior diagnostics to GCC. For example the Expressive Diagnostics page shows examples where Clang's diagnostics were indeed superior to GCC 4.2, which was released in 2007. GCC has improved considerably since then. This page revisits the examples using recent versions of GCC and add further interesting examples.1

Column Numbers and Caret Diagnostics

GCC has printed column numbers for several releases and the 4.8 release series added caret diagnostics too.

For this source:

1 # include 2 void f () { () { 3 printf ( " %.*d " ); ); 4 }

$ gcc-6.0 -fsyntax-only -Wformat format-strings.c

format-strings.c:3:14: warning: field width specifier ‘*’ expects a matching ‘int’ argument [-Wformat=] printf("%*d"); ^ format-strings.c:3:15: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=] printf("%*d"); ^

$ clang-3.1 -fsyntax-only format-strings.c

format-strings.c:3:14: warning: '.*' specified field precision is missing a matching 'int' argument printf("%.*d"); ~~^~

GCC detects that there are two errors, while Clang only detects one.

No Pretty Printing of Expressions in Diagnostics

Since GCC 4.8 has caret diagnostics, it does not need to pretty print expressions:2

1 void foo ( char ** p , char ** q ) **** 2 { 3 ( p - q )(); )(); 4 p (); (); 5 }

$ gcc-4.2 -fsyntax-only t.c

#‘exact_div_expr’ not supported by pp_c_expression#’ t.c:3:10: error: called object is not a function t.c:4:4: error: called object ‘p’ is not a function

$ clang -fsyntax-only t.c

t.c:3:10: error: called object type 'long' is not a function or function pointer (p - q)(); ~~~~~~~^ t.c:4:4: error: called object type 'char **' is not a function or function pointer p(); ~^

GCC 6 prints an underlined range highlighting the bogus expression (also detects when the object is actually a variable declared elsewhere, and points to the declaration):

$ gcc-6 -fsyntax-only t.c

t.c: In function ‘foo’: t.c:3:6: error: called object is not a function or function pointer (p - q)(); ~~~^~~~ t.c:4:3: error: called object ‘p’ is not a function or function pointer p(); ^ t.c:1:17: note: declared here void foo(char **p, char **q) ^

Another example:

1 struct a { 2 virtual int bar (); (); 3 }; }; 4 5 struct foo : public virtual a { 6 }; }; 7 8 void test ( foo * P ) { ) { 9 return P -> bar () + * P ; ->() + * 10 }

$ gcc-4.2 t.cc

t.cc: In function 'void test(foo*)': t.cc:9: error: no match for 'operator+' in '(((a*)P) + (*(long int*)(P->foo::<anonymous>.a::_vptr$a + -0x00000000000000020)))->a::bar() + * P' t.cc:9: error: return-statement with a value, in function returning 'void'

$ gcc-6 t.cc

t.cc: In function 'void test(foo*)': t.cc:9:21: error: no match for 'operator+' (operand types are 'int' and 'foo') return P->bar() + *P; ~~~~~~~~~^~~~ t.cc:9:24: error: return-statement with a value, in function returning 'void' [-fpermissive] return P->bar() + *P; ^

$ clang t.cc

t.cc:9:21: error: invalid operands to binary expression ('int' and 'foo') return P->bar() + *P; ~~~~~~~~ ^ ~~

Typedef Preservation and Selective Unwrapping

GCC has behaved similarly to Clang since at least GCC 4.4, and GCC 4.8 adds also the caret:

1 typedef float __m128 __attribute__ (( vector_size ( 32 ))); (())); 2 void f () () 3 { 4 __m128 myvec [ 2 ]; ]; 5 int const * P ; 6 myvec [ 1 ]/ P ; ]/ 7 }

$ gcc-4.8 -fsyntax-only t.c

t.c: In function 'f': t.c:6:11: error: invalid operands to binary / (have '__m128' and 'const int *') myvec[1]/P; ^

$ clang-3.1 -fsyntax-only t.c

t.c:6:11: error: can't convert between vector values of different size ('__m128' and 'int const *') myvec[1]/P; ~~~~~~~~^~

For this example gcc hasn't improved, but g++ shows an "aka" like Clang:

1 typedef int pid_t ; 2 void f () { () { 3 pid_t myvar ; 4 myvar = myvar . x ; 5 }

$ g++-4.8 -fsyntax-only t.c

t.c: In function ‘void f()’: t.c:4:17: error: request for member ‘x’ in ‘myvar’, which is of non-class type ‘pid_t {aka int}’ myvar = myvar.x; ^

$ clang-3.1 -fsyntax-only t.c

t.c:4:17: error: member reference base type 'pid_t' (aka 'int') is not a structure or union myvar = myvar.x; ~~~~~ ^

Automatic Macro Expansion

1 # define MYMAX(A,B) __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) 2 struct mystruct { }; { }; 3 void f () { () { 4 int X ; 5 float F ; 6 struct mystruct P ; 7 X = MYMAX ( P , F ); ); 8 }

GCC 4.8 tracks macro expansions by default and uses it to print macro expansions with caret:

$ gcc-4.8 -fsyntax-only t.c

t.c:1:94: error: invalid operands to binary < (have ‘struct mystruct’ and ‘float’) #define MYMAX(A,B) __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) ^ t.c:7:7: note: in expansion of macro 'MYMAX' X = MYMAX(P, F); ^

$ clang-3.1 -fsyntax-only t.c

t.c:7:7: error: invalid operands to binary expression ('typeof (P)' (aka 'struct mystruct') and 'typeof (F)' (aka 'float')) X = MYMAX(P, F); ^~~~~~~~~~~ t.c:1:94: note: expanded from macro 'MYMAX' #define MYMAX(A,B) __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) ~~~ ^ ~~~ t.c:7:5: error: assigning to 'int' from incompatible type 'void' X = MYMAX(P, F); ^ ~~~~~~~~~~~

Quality of Implementation and Attention to Detail

GCC's error recovery is much better now:

1 foo_t * P = 0 ;

$ gcc-4.8 t.c

t.c:1:1: error: unknown type name 'foo_t' foo_t *P = 0; ^

$ clang-3.1 t.c

t.c:1:1: error: unknown type name 'foo_t' foo_t *P = 0; ^

GCC and G++ handle missing semicolons after structs and classes:

1 template < class T > 2 class a {} {} 3 class temp {}; {}; 4 a < temp > b ; 5 struct b { 6 }

$ g++-4.8 t.cc

t.cc:2:10: error: expected ';' after class definition class a {} ^ t.cc:6:1: error: expected ';' after struct definition } ^

$ clang++-3.1 t.cc

t.cc:2:11: error: expected ';' after class class a {} ^ ; tsc.cc:6:2: error: expected ';' after struct } ^ ;

The following examples are not on the original Clang page, but show some cases where G++ produces better diagnostics.

Recursive template instantiations

GCC can detect a recursive template instantiation and avoids giving a cascade of diagnostics:

1 template < int N > struct X { 2 static const int value = X < N - 1 >:: value ; >:: 3 }; }; 4 template struct X < 1000 >; >;

$ clang-3.1 -fsyntax-only recursive.C

recursive.C:2:28: fatal error: recursive template instantiation exceeded maximum depth of 1024 static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<-24>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<-23>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<-22>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<-21>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<-20>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: (skipping 1015 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all) recursive.C:2:28: note: in instantiation of template class 'X<996>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<997>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<998>' requested here static const int value = X<N-1>::value; ^ recursive.C:2:28: note: in instantiation of template class 'X<999>' requested here static const int value = X<N-1>::value; ^ recursive.C:4:17: note: in instantiation of template class 'X<1000>' requested here template struct X<1000>; ^ recursive.C:2:28: note: use -ftemplate-depth-N to increase recursive template instantiation depth static const int value = X<N-1>::value; ^ 1 error generated.

$ gcc-4.8 -fsyntax-only recursive.C

recursive.C:2:36: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘struct X<100>’ static const int value = X<N-1>::value; ^ recursive.C:2:36: recursively required from ‘const int X<999>::value’ recursive.C:2:36: required from ‘const int X<1000>::value’ recursive.C:4:17: required from here recursive.C:2:36: error: incomplete type ‘X<100>’ used in nested name specifier static const int value = X<N-1>::value; ^

C++ templates errors

This invalid code should say typename T::type in line 1:

1 template < class T > void f ( T :: type ) { } ::) { } 2 struct A { }; { }; 3 void g () () 4 { 5 A a ; 6 f < A >( a ); >(); 7 }

Neither GCC nor Clang gives any clue what the real problem is:

$ g++-4.8 missing-typename.cc

missing-typename.cc:1:33: error: variable or field 'f' declared void template<class T> void f(T::type) { } ^ missing-typename.cc: In function 'void g()': missing-typename.cc:6:4: error: 'f' was not declared in this scope f<A>(a); ^ missing-typename.cc:6:7: error: expected primary-expression before '>' token f<A>(a); ^

$ clang++-3.1 -fsyntax-only t.cc

t.cc:1:24: error: variable 'f' declared as a template template<class T> void f(T::type) { } ~~~~~~~~~~~~~~~~~ ^ t.cc:1:34: error: expected ';' at end of declaration template<class T> void f(T::type) { } ^ ; t.cc:1:35: error: expected unqualified-id template<class T> void f(T::type) { } ^ t.cc:6:5: error: use of undeclared identifier 'f' f<A>(a); ^ t.cc:6:7: error: 'A' does not refer to a value f<A>(a); ^ t.cc:2:8: note: declared here struct A { }; ^

Clang's carets and range highlighting don't help identify the problem, and the poor error recovery that means "A" is reported as undeclared doesn't help either.

If you add the missing typename then G++ does better:

1 template < class T > void f ( typename T :: type ) { } ::) { } 2 struct A { }; { }; 3 void g () () 4 { 5 A a ; 6 f < A >( a ); >(); 7 }

$ g++-4.7 deduce.cc

deduce.cc: In function 'void g()': deduce.cc:6:10: error: no matching function for call to 'f(A&)' deduce.cc:6:10: note: candidate is: deduce.cc:1:24: note: template<class T> void f(typename T::type) deduce.cc:1:24: note: template argument deduction/substitution failed: deduce.cc: In substitution of 'template<class T> void f(typename T::type) [with T = A]': deduce.cc:6:10: required from here deduce.cc:1:24: error: no type named 'type' in 'struct A'

$ g++-4.8 deduce.cc

deduce.cc: In function 'void g()': deduce.cc:6:10: error: no matching function for call to 'f(A&)' f<A>(a); ^ deduce.cc:6:10: note: candidate is: f<A>(a); ^ deduce.cc:1:24: note: template<class T> void f(typename T::type) template<class T> void f(typename T::type) { } ^ deduce.cc:1:24: note: template argument deduction/substitution failed: template<class T> void f(typename T::type) { } ^ deduce.cc: In substitution of 'template<class T> void f(typename T::type) [with T = A]': deduce.cc:6:10: required from here deduce.cc:1:24: error: no type named 'type' in 'struct A' template<class T> void f(typename T::type) { } ^

$ clang++-3.1 -fsyntax-only t.cc

t.cc:6:5: error: no matching function for call to 'f' f<A>(a); ^~~~ t.cc:1:24: note: candidate template ignored: substitution failure [with T = A] template<class T> void f(typename T::type) { } ^

GCC correctly identifies the precise reason why the template argument deduction fails as " no type named 'type' in 'struct A' ", while Clang only says "substitution failure" .

TODO

Examples of detailed explanations for overload resolution failures.

Mention briefly other recent features: #pragma GCC diagnostic/push/pop ,

Clang Warnings, Richard Trieu, 2013-09-04.

--