1

This was motivated by this article (page 5)

template<class T> 
T const &f(T const &a, T const &b){
    return (a > b ? a : b);
}

template int const &f<int>(int const &, int const &);

int main(){
    int x = 0, y = 0;
    short s = 0;
    f(x, y);     // OK
    f(x, s);     // Is this call well-formed?
}

Is the call 'f(x, s)' well-formed? I assumed that since the function template 'f' is explicitly instantiated, standard conversions would be applied and hence 'short s' would be converted to 'int' to match the call to the explicit specialization 'f<int>'. But it appears that this is ill-formed?

Which part of the Standard talks about the applicable rules in this context?

Chubsdad
  • 24,777
  • 4
  • 73
  • 129
  • 1
    If I understand where you're coming from with this, you may want to read about Alexandrescu's min/max implementation at http://www.drdobbs.com/cpp/184403774 - old but interesting. (I haven't reread it to see how it's aged) – Tony Delroy Sep 16 '10 at 05:10
  • It's usually a bad idea to specialize a function template. See Herb Sutter's ["Why not Specialize Function Templates?"](http://www.gotw.ca/publications/mill17.htm) for alternatives. – James McNellis Sep 16 '10 at 05:30

3 Answers3

5

No, the call f(x, s) is not well-formed. Since you do not explicitly state the specialization to be used, the compiler uses argument deduction to attempt to instantiate the function template; this fails because x and s have different types so T is ambiguous.

The applicable rule is in the specification of the overload resolution process in 13.3.1:

In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way.

14.8.3/1 is also relevant:

For each function template, if the argument deduction and checking succeeds, the template arguments (deduced and/or explicit) are used to instantiate a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails, no such function is added to the set of candidate functions for that template.

The function template is explicitly instantiated for T = int, but the compiler doesn't know that it should use this instantiation until after it performs template argument deduction to determine what T should be.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • I am unable to correlate this with the page 5 of Scott's article. Did the rule change over the years in this regards? – Chubsdad Sep 16 '10 at 06:25
  • @Chubsdad: For an article that old, it's hard to verify that it was ever correct, but I suppose perhaps. I rather doubt it, though, that snippet is probably just in error. For example, in the first snippet on that page, he passes the wrong number of template arguments to `max`. I guess he wasn't quite as thorough back then :vP . – Potatoswatter Sep 16 '10 at 07:01
  • 1
    I would upvote this, but it's not an explicit specialization but an explicit *instantiation*. – Johannes Schaub - litb Sep 16 '10 at 09:50
  • @Chubsdad: I didn't actually read the article; if I have time I'll go back and read it. – James McNellis Sep 16 '10 at 15:11
3

The call f(x, s) is syntactically well-formed, but the compiler will not be able to deduce the template parameter T from it because is could a int or a short (because of the first and second arguments). Instantiating the template does not help, that only indicates the compiler to compile that specialization and add it to the generated object file.

If you want the call to cast s to a int automatically, use f<int>(x, s).

jbernadas
  • 2,540
  • 18
  • 12
  • @jbernadas: I added a reference to the article from Scott just now. According to this article (of course very old), this should work. I am not sure if the rules changed since then. Is there any reference to the Standard section/para? – Chubsdad Sep 16 '10 at 04:10
  • Maybe it's compiler-specific. I tried in Visual Studio 2010 and it failed, trying it in g++ might be a good idea. – jbernadas Sep 16 '10 at 04:21
  • 1
    g++ 4.1.2 fails with "no matching function" message. See `http://codepad.org/WVWAXkGK` – rlduffy Sep 16 '10 at 05:21
  • 3
    If `T` is not explicitly specified and if argument deduction fails, how can we say the call is well-formed? If overload resolution fails, the call is ill-formed. – James McNellis Sep 16 '10 at 05:37
  • +1 to @James. Why do you first say it's well-formed but then say it's ill-formed (because deduction fails)? – Johannes Schaub - litb Sep 16 '10 at 09:49
  • @James: I meant syntactically well-formed, but no semantically, i.e.: the compiler knows that it is a function call, but it does not know which function to call. – jbernadas Sep 16 '10 at 16:54
1

An explicitly instantiated specialization doesn't have any higher priority or preferential treatment. It simply exists in its entirety from the point of instantiation. Useful for libraries.

The compiler simply can't figure out which argument to convert, and gets stuck just as it would without the extra declaration.

By the way, if you return it a reference to an argument which was converted, it will be dangling once the temporary expires. If the arguments are references to different types, there is no way to properly form the return value.

Here is my updated min:

#include <type_traits>

template< typename A, typename B >
typename std::common_type< A, B >::type // cannot return ref
my_min( A const &a, B const &b ) {
    return a < b? a : b;
}

 // enable_if< is_same< A, B > > the old, return-by-reference impl

int main() {
    int q = my_min( short(5), long(3) );
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Reference is fine if both are of type 'T'. The problem occurs when one it 'T' and the other is 'U'. Isn't it? – Chubsdad Sep 16 '10 at 07:13
  • @Chubs: Not quite, as I explained, but sometimes. Be careful not to return a reference to a temporary. Or if you do, mind how you use it. (The problem does not occur with `std::max` unless you explicitly specify the template arguments, or manually cast, since implicit conversions are precluded by non-deduction.) – Potatoswatter Sep 16 '10 at 07:17
  • If you are with C++03 you can use my `promote` template instead of `common_type`: http://stackoverflow.com/questions/2426330/uses-of-a-c-arithmetic-promotion-header/2450157#2450157. Also note that the comparison in `f` cannot yield a temporary, because both branches have the same type. I agree with @Chubsdad about that - can you please provide an example where a local temporary is introduced? – Johannes Schaub - litb Sep 16 '10 at 09:53
  • @Johannes: `short s = 5; int const &t = std::min( s, 9 );` (oops, I replied @litb before) – Potatoswatter Sep 17 '10 at 00:23
  • @Potatoswatter note that this does not create a local temporary. So `int const t = std::min(s, 9);` is fine :) It's useful to have no copy be introduced for things like strings, pair and stuff, to avoid needless copies being created. In that case, when calling with `std::min(a, b)` you can be sure there is never a temporary created even at the call-side, so a `T const& t = std::min(a, b);` is fine. Only when explicitly specifying an argument the issue comes up, but then one knows not to bind to a reference, I think :) – Johannes Schaub - litb Sep 17 '10 at 14:59
  • @Johannes: `s` is converted to an `int` temporary by the call, which is not extended to the lifetime of `t`. The return value is invalid. Yes, I noted this does not happen without explicit specification or an explicit cast. – Potatoswatter Sep 17 '10 at 16:43