C++ inconsistency between gcc and clang

2019-02-07 18:08发布

问题:

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?

回答1:

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.