I came across a C++ inconsistency between gcc
(versions 4.8.1
, 4.8.2
) and clang
(versions 3.3
, 3.4
). I wonder which one is correct. Here's the program:
template < typename T > struct Result {};
template < typename T > struct Empty {};
template < typename T >
struct Bad_Type_Fcn {
typedef typename Empty< T >::type type;
};
template < typename T >
Result< T >
f( const T& ) {
return Result< T >();
}
template< class U >
Result< typename Bad_Type_Fcn< U >::type >
f( const U&, int ) {
return Result< typename Bad_Type_Fcn< U >::type >();
}
int main() {
(void)f< int >(42);
}
Clearly, this code is not meant to do anything; it is an aggressive simplification of something that appears in the Boost Range library (with f
simplifying make_iterator_range
). The Bad_Type_Fcn
is a type function (technically, a struct
) which should never be instantiated, because Empty<T>::type
never exists, for any T
. The presence of this struct
and of the second template specialization of f()
is not an error in itself. IRL, f()
provides some functionality for certain types for which Bad_Type_Fcn
is not empty. However that is not the concern here, which is why I simplified those out. I still want f()
to work for types where Bad_Type_Fcn
is empty.
I'm compiling with {g++|clang++} [-std=c++0x] -pedantic -Wall -Wextra -c
. The language standard selection doesn't seem to make a difference. With clang
, the program compiles without errors or warnings. With gcc
, I get an error:
weird.cpp: In instantiation of ‘struct Bad_Type_Fcn<int>’:
weird.cpp:17:5: required by substitution of ‘template<class U> Result<typename Bad_Type_Fcn<T>::type> f(const U&, int) [with U = int]’
weird.cpp:22:26: required from here
weird.cpp:6:43: error: no type named ‘type’ in ‘struct Empty<int>’
typedef typename Empty< T >::type type;
What seems to be happening is that clang
eliminates the second overload of f()
, probably(?) on the basis that the call is made with 1 argument only, integer 42
, while the second overload requires 2 arguments. On the other hand, gcc
doesn't eliminate the second overload, and instead tries to instantiate struct Bad_Type_Fcn<int>
, which results in an error.
The inconsistency disappears if I remove the explicit instantiation in the call to f()
, and write (void)f(42);
instead.
Which of the compilers is correct?
Yes, for C code Clang and GCC are compatible (they both use the GNU Toolchain for linking, in fact.) You just have to make sure that you tell clang to create compiled objects and not intermediate bitcode objects. C ABI is well-defined, so the only issue is storage format.
Clang is much faster and uses far less memory than GCC. Clang aims to provide extremely clear and concise diagnostics (error and warning messages), and includes support for expressive diagnostics. GCC's warnings are sometimes acceptable, but are often confusing and it does not support expressive diagnostics.
Clang is a compiler front end for the C, C++, Objective-C, and Objective-C++ programming languages, as well as the OpenMP, OpenCL, RenderScript, CUDA, and HIP frameworks. It acts as a drop-in replacement for the GNU Compiler Collection (GCC), supporting most of its compilation flags and unofficial language extensions.
The Clang project provides a language front-end and tooling infrastructure for languages in the C language family (C, C++, Objective C/C++, OpenCL, CUDA, and RenderScript) for the LLVM project.
I remember a WG21 core discussion about this, and one of the Clang developers defended their position by citing 14.7.1p7
If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.
On the other hand, for an ill-formed program (which is the case here when doing the required instantiation), there is no such notion of "the correct function to call", so I agree to the position of another guy in that discussion who said that he can't see that this allows Clang to go that route.
In the example of p7 it shows code that is well-formed both with and without doing the additional instantiation.
In any case, even if Clang is allowed to do it, the well-formedness of your program would then rely on particular happenstances (unspecified behavior). The Standard therefore doesn't anymore require your program to be accepted, and honestly I don't know what that means. I regard such code as being ill-formed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With