views:

135

answers:

3

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?

+3  A: 

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
@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
Maybe it's compiler-specific. I tried in Visual Studio 2010 and it failed, trying it in g++ might be a good idea.
jbernadas
g++ 4.1.2 fails with "no matching function" message. See `http://codepad.org/WVWAXkGK`
rlduffy
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
+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
@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
+5  A: 

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
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
@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
I would upvote this, but it's not an explicit specialization but an explicit *instantiation*.
Johannes Schaub - litb
@Johannes: Thanks you.
James McNellis
@Chubsdad: I didn't actually read the article; if I have time I'll go back and read it.
James McNellis
+1  A: 

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
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
@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
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
@Johannes: `short s = 5; int const ` (oops, I replied @litb before)
Potatoswatter
@Potatoswatter note that this does not create a local temporary. So `int const t = std::min<int>(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` 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
@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