28

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?

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
Matei David
  • 2,322
  • 3
  • 23
  • 36
  • GCC shouldn't be instantiating the second overload in the code sample given. – Simple Mar 07 '14 at 18:47
  • 1
    You might want to try it out on gcc 4.9; it's pretty interesting really especially as this should be a SFINAE case from what I can see. Very interesting test case, and great work reducing it so much. – Matthieu M. Mar 07 '14 at 18:49
  • 2
    +1, but terminology correction (in questions like this, it can matter): there is no function template *specialisation* involved (since partial specialisation doesn't exist for them). You have two *distinct* function templates, which happen to have the same name and thus *overload* each other. – Angew is no longer proud of SO Mar 07 '14 at 18:51
  • 2
    @MatthieuM.: It took a few days, as you can imagine... – Matei David Mar 07 '14 at 18:52
  • 2
    I don't think the compiler should be instantiating #2 at all, but GCC's case it seems to be, and it's failing to compile because it's not an SFINAE failure. SFINAE doesn't apply to the `typedef typename Empty< T >::type type` inside the body of `Bad_Type_Fcn`. You'd need to make the nested `type` typedef disappear if `Empty::type` doesn't exist for the SFINAE to work. – Simple Mar 07 '14 at 18:53
  • I remember reading somewhere that SFINAE only applies to function arguments, not to the return value. If that's true, this wouldn't be an SFINAE issue. – Matei David Mar 07 '14 at 19:01
  • @mmd: SFINAE definitely applies to the result, `template typename boost::enable_if<...>::type func(...)` is the default C++03 way of using SFINAE after all. – Matthieu M. Mar 07 '14 at 19:09
  • @Simple: interesting, indeed if one replaces `Bad_Type_Fcn` by `Empty` (directly), then gcc compiles without an issue. So it would boil down to the level at which SFINAE applies, well, now comes the time to find this in the Standard. – Matthieu M. Mar 07 '14 at 19:12
  • @MatthieuM. Another way to have SFINAE applied and remove the GCC error is to define `template < typename T > using Bad_Type_Fcn = typename Empty< T >::type;` as an alias, then use `Result< Bad_Type_Fcn< U > >` directly. I've always found such GCC/Clang differences when implementing SFINAE tests and resort to this "alias" method to avoid errors. If I remember well, GCC 4.9 does not change this behavior. Unfortunately, I don't speak Standardese :-) – iavr Mar 07 '14 at 19:25
  • *"The presence of this struct and of the second template specialization of f() is not an error in itself."* What about [temp.res]/8 "If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required." – dyp Mar 07 '14 at 19:29
  • @dyp: I do not think it applies, one could conceivably specialize `Empty` for a set of types and then those instantiations would work. And indeed this is what happens in Boost.Range. – Matthieu M. Mar 07 '14 at 19:35
  • @MatthieuM. It's also not the problem clang complains about, we can easily check that by adding a specialization e.g. `Empty`. But I think technically, the program is ill-formed, NDR. – dyp Mar 07 '14 at 19:37
  • The behaviour doesn't change if I add a valid but irrelevant specialization: `template <> struct Bad_Type_Fcn { typedef void type; };`. – Matei David Mar 07 '14 at 19:37
  • @MatthieuM. Oops, I meant *it's not the problem **gcc** complains about*. – dyp Mar 07 '14 at 19:47
  • @dyp: it does seem to be the problem though, if I specialize `Empty` for `int` then [it's okay](http://ideone.com/xGzgTN) because there is no error in instantiating `Bad_Type_Fcn::type` any longer. This immediate context thing is tricky though, especially with aliases changing the boundaries. – Matthieu M. Mar 07 '14 at 19:57

1 Answers1

17

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Doesn't this contradict [temp.over]/1? Substitution is a part of deduction, and deduction is mandatory for each function template. – dyp Mar 07 '14 at 19:42
  • @dyp can you please elaborate on what you mean by "Doesn't this contradict [temp.over]/1"? – Johannes Schaub - litb Mar 07 '14 at 19:44
  • [temp.over]/1 seems to describe the overload resolution process involving function templates. It specifies that template argument deduction takes place *for each function template*. Therefore, substitution should also occur as part of this process, which leads to an invalid type as far as I can tell. – dyp Mar 07 '14 at 19:46
  • @dyp yes, that is correct. But I don't see where the contradiction is. Notice that the invalid type has the invalidness rising up in a non-immediate context (the body of an instantiated class template). This will yield a hard error, and not simply a deduction failure. – Johannes Schaub - litb Mar 07 '14 at 19:47
  • Yes exactly. But how does 14.7.1[temp.inst]/7 open a loophole for this? As far as I can tell, it's a hard error, no matter if the instantiation actually takes place or not - it's an invalid type .. well you have to instantiate to see that, of course, that's where I'm confused. – dyp Mar 07 '14 at 19:49
  • 3
    @dyp if you don't instantiate the class template `Bad_Type_Fcn`, then you don't rise the error. As I said, instantiation really is *required* as it is part of the substitution process, but 14.7.1[temp.inst]/7 enables clang to not do the instantiation. I think the type then stays incomplete and then `Bad_Type_Fcn< U >::type` is an immediate deduction error since `invalid_class_type::member` always fails. – Johannes Schaub - litb Mar 07 '14 at 19:52
  • However, surely clang didn't first figure out that the second template isn't viable, just to then start template argument deduction with the modification of omitting the instantiation :) I bet it just completely ignored the template and didn't even start deduction (which in *this* case is *as-if* deduction had taken place, modulo instantiation of the class template). – Johannes Schaub - litb Mar 07 '14 at 19:57
  • @JohannesSchaub-litb: Thanks for the reply. I'm having trouble following the discussion because I don't have access to (or I don't know where to find) the related documents. Can you post some links, in case they are public? – Matei David Mar 07 '14 at 20:23
  • @mmd i am sorry I don't have links to it – Johannes Schaub - litb Mar 07 '14 at 20:25
  • Also, as I said in the main text, if I remove the explicit specialization in the function call, the code compiles with both `clang` and `gcc`. Would you consider that program to be ill-formed as well? (In that case, I'd file a bug report with boost.) Or is it just the explicit specialization that makes it ill-formed? – Matei David Mar 07 '14 at 20:27
  • 4
    @mmd I think if you remove the explicit template argument, the program isn't ill-formed anymore because type deduction then fails (match error). The argument list is `(int)` and the parameter list is `(const U&, int)`. It's a problem with the explicitly specifying of the template argument, because then the argument will be substituted before doing the match of `(int)` vs `(const int&, int)`. – Johannes Schaub - litb Mar 07 '14 at 20:39
  • Thanks again. I posted a note about this on the boost users list: http://article.gmane.org/gmane.comp.lib.boost.user/81227 – Matei David Mar 07 '14 at 23:26
  • @JohannesSchaub-litb I didn't understand your explanation of the deduction case and the "match error", so I [asked a question here](http://stackoverflow.com/q/22300620/420683). – dyp Mar 14 '14 at 00:40