25

Consider the following simple (to the extent that template questions ever are) example:

#include <iostream>

template <typename T>
struct identity;

template <>
struct identity<int> {
    using type = int;
};

template<typename T> void bar(T, T ) { std::cout << "a\n"; }
template<typename T> void bar(T, typename identity<T>::type) { std::cout << "b\n"; }

int main ()
{
    bar(0, 0);
}

Both clang and gcc print "a" there. According to the rules in [temp.deduct.partial] and [temp.func.order], to determine partial ordering, we need to synthesize some unique types. So we have two attempts at deduction:

+---+-------------------------------+-------------------------------------------+
|   | Parameters                    | Arguments                                 |
+---+-------------------------------+-------------------------------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA                          |
| b | T, T                          | UniqueB, typename identity<UniqueB>::type |
+---+-------------------------------+-------------------------------------------+

For deduction on "b", according to Richard Corden's answer, the expression typename identity<UniqueB>::type is treated as a type and is not evaluated. That is, this will be synthesized as if it were:

+---+-------------------------------+--------------------+
|   | Parameters                    | Arguments          |
+---+-------------------------------+--------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA   |
| b | T, T                          | UniqueB, UniqueB_2 |
+---+-------------------------------+--------------------+

It's clear that deduction on "b" fails. Those are two different types so you cannot deduce T to both of them.

However, it seems to me that the deduction on A should fail. For the first argument, you'd match T == UniqueA. The second argument is a non-deduced context - so wouldn't that deduction succeed iff UniqueA were convertible to identity<UniqueA>::type? The latter is a substitution failure, so I don't see how this deduction could succeed either.

How and why do gcc and clang prefer the "a" overload in this scenario?

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    http://stackoverflow.com/a/1182688/4326278 contains relevant info, I think. – bogdan Jul 13 '15 at 22:20
  • @bogdan That makes sense from the perspective of just treatying `typename identity::type` as `UniqueB_2`. But why would the deduction on "a" succeed? I reworded the question somewhat. – Barry Jul 16 '15 at 16:40
  • As far as I can tell, that applies not only to generating the unique arguments, but also to adjusting the parameters: `T, typename identity::type` becomes `T, U`, so deduction succeeds. The algorithm, however, clearly does a few more things that aren't specified in the standard either. In this case, it 'remembers' that `T` is actually used within the construct that `U` replaced so that, at the end of the deduction process, if `T` hasn't been deduced from somewhere else, deduction still fails overall (there's a hint at this in [14.8.2.4p11]). – bogdan Jul 16 '15 at 17:03
  • That's why deduction fails in the example from [the other question](http://stackoverflow.com/q/31391172/4326278), using only one parameter (`bar(T)` vs. `bar(typename identity::type)`). As I said, the whole thing is poorly specified. [Issue 1391](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1391) will likely change the treatment of non-deduced contexts and make things better specified, although still far from perfect. – bogdan Jul 16 '15 at 17:08
  • On a side note, another case that's not specified is why `template void f(T, T)` is more specialized than `template void f(T, U)`. Nowhere does the standard say that the deductions for different pairs of types need to be consistent during partial ordering; the algorithm as specified only deals with the pairs of types in isolation. My version: for the parameters for which the function call has arguments, deduction must succeed for a synthesized function call using the invented args from `A` against a synthesized template declaration using the params from `P`. – bogdan Jul 16 '15 at 17:22
  • @bogdan Yes, the addition of "If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering." would clear this up. It wouldn't address the `typename identity::type --> UniqueB_2` issue though. Do you want to combine all of this into an answer? – Barry Jul 16 '15 at 17:26
  • @bogdan Why not specified? The former-to-latter deduction succeeds (`T == U == UniqueA`), the latter-to-former deduction fails (deducing `T,T` against `UniqueB`, `UniqueC`). – Barry Jul 16 '15 at 17:28
  • Where does it say, *in the algorithm for partial ordering*, that deductions for different pairs of types need to be consistent? It only says that deduction needs to succeed for each pair, and in this case it does. It's just that the two types deduced for `T` will be different. – bogdan Jul 16 '15 at 17:31
  • @bogdan I see what you're saying. Basically need something like [temp.deduct.type]/2 for the partial ordering case. Damn. – Barry Jul 16 '15 at 17:39
  • Or just formulate the start of the algorithm in terms of deduction from a function call, like I did in my version (two comments above). Regarding `typename identity::type --> UniqueB_2`, that should also be solved by *that P is not used to determine the ordering*. If I understand that correctly, it's supposed to mean *that `P` / `A` pair is not used* (I don't see how it would make sense otherwise), in which case you won't get to generating a unique type for deduction the opposite way. – bogdan Jul 16 '15 at 17:50
  • Actually, scratch the second part of my previous comment. I guess *P is not used* could mean just that. Using the names in [14.8.2.4p10], it could mean that, if `F` has such a parameter and `G` doesn't, there will be fewer parameter pairs involved in determining whether `G` is at least as specialized as `F` than the other way around. I'll have to think about it some more to see what effects this would have on some examples. Anyway, if nobody comes up with an authoritative answer, I'll try to write one, say, tomorrow? – bogdan Jul 16 '15 at 19:00
  • I'm sorry, I said I'd write an answer "tomorrow"; obviously that didn't happen, and, anyway, I'm not sure it would bring something useful to the table. What's really needed is an answer from an authoritative source, and clear text in the standard. I think I'll hold off writing an answer for now. – bogdan Jul 21 '15 at 13:43
  • The proposed resolution of http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1391 may handle this issue. – T.C. Jul 27 '15 at 18:47
  • @T.C. Yeah it looks like it will. The change to temp.deduct.partial would exclude the `typename identity::type` parameter from consideration. – Barry Jul 27 '15 at 19:01
  • @T.C. We discussed 1391 in comments and chat last week, and, after thinking about it some more, I put together a list of (what I think are) problems in the proposed resolution. It's in my answer below; I'd very much like to know what you guys think. – bogdan Jul 30 '15 at 22:33

2 Answers2

27

As discussed in the comments, I believe there are several aspects of the function template partial ordering algorithm that are unclear or not specified at all in the standard, and this shows in your example.

To make things even more interesting, MSVC (I tested 12 and 14) rejects the call as ambiguous. I don't think there's anything in the standard to conclusively prove which compiler is right, but I think I might have a clue about where the difference comes from; there's a note about that below.

Your question (and this one) challenged me to do some more investigation into how things work. I decided to write this answer not because I consider it authoritative, but rather to organize the information I have found in one place (it wouldn't fit in comments). I hope it will be useful.


First, the proposed resolution for issue 1391. We discussed it extensively in comments and chat. I think that, while it does provide some clarification, it also introduces some issues. It changes [14.8.2.4p4] to (new text in bold):

Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A. If a particular P contains no template-parameters that participate in template argument deduction, that P is not used to determine the ordering.

Not a good idea in my opinion, for several reasons:

  • If P is non-dependent, it doesn't contain any template parameters at all, so it doesn't contain any that participate in argument deduction either, which would make the bold statement apply to it. However, that would make template<class T> f(T, int) and template<class T, class U> f(T, U) unordered, which doesn't make sense. This is arguably a matter of interpretation of the wording, but it could cause confusion.
  • It messes with the notion of used to determine the ordering, which affects [14.8.2.4p11]. This makes template<class T> void f(T) and template<class T> void f(typename A<T>::a) unordered (deduction succeeds from first to second, because T is not used in a type used for partial ordering according to the new rule, so it can remain without a value). Currently, all compilers I've tested report the second as more specialized.
  • It would make #2 more specialized than #1 in the following example:

    #include <iostream>
    
    template<class T> struct A { using a = T; };
    
    struct D { };
    template<class T> struct B { B() = default; B(D) { } };
    template<class T> struct C { C() = default; C(D) { } };
    
    template<class T> void f(T, B<T>) { std::cout << "#1\n"; } // #1
    template<class T> void f(T, C<typename A<T>::a>) { std::cout << "#2\n"; } // #2
    
    int main()
    {
       f<int>(1, D());
    }
    

    (#2's second parameter is not used for partial ordering, so deduction succeeds from #1 to #2 but not the other way around). Currently, the call is ambiguous, and should arguably remain so.


After looking at Clang's implementation of the partial ordering algorithm, here's how I think the standard text could be changed to reflect what actually happens.

Leave [p4] as it is and add the following between [p8] and [p9]:

For a P / A pair:

  • If P is non-dependent, deduction is considered successful if and only if P and A are the same type.
  • Substitution of deduced template parameters into the non-deduced contexts appearing in P is not performed and does not affect the outcome of the deduction process.
  • If template argument values are successfully deduced for all template parameters of P except the ones that appear only in non-deduced contexts, then deduction is considered successful (even if some parameters used in P remain without a value at the end of the deduction process for that particular P / A pair).

Notes:

  • About the second bullet point: [14.8.2.5p1] talks about finding template argument values that will make P, after substitution of the deduced values (call it the deduced A), compatible with A. This could cause confusion about what actually happens during partial ordering; there's no substitution going on.
  • MSVC doesn't seem to implement the third bullet point in some cases. See the next section for details.
  • The second and third bullet points are intented to also cover cases where P has forms like A<T, typename U::b>, which aren't covered by the wording in issue 1391.

Change the current [p10] to:

Function template F is at least as specialized as function template G if and only if:

  • for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G, and,
  • when performing deduction using the transformed F as the argument template and G as the parameter template, after deduction is done for all pairs of types, all template parameters used in the types from G that are used to determine the ordering have values, and those values are consistent across all pairs of types.

F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.

Make the entire current [p11] a note.

(The note added by the resolution of 1391 to [14.8.2.5p4] needs to be adjusted as well - it's fine for [14.8.2.1], but not for [14.8.2.4].)


For MSVC, in some cases, it looks like all template parameters in P need to receive values during deduction for that specific P / A pair in order for deduction to succeed from A to P. I think this could be what causes implementation divergence in your example and others, but I've seen at least one case where the above doesn't seem to apply, so I'm not sure what to believe.

Another example where the statement above does seem to apply: changing template<typename T> void bar(T, T) to template<typename T, typename U> void bar(T, U) in your example swaps results around: the call is ambiguous in Clang and GCC, but resolves to b in MSVC.

One example where it doesn't:

#include <iostream>

template<class T> struct A { using a = T; };
template<class, class> struct B { };

template<class T, class U> void f(B<U, T>) { std::cout << "#1\n"; }
template<class T, class U> void f(B<U, typename A<T>::a>) { std::cout << "#2\n"; }

int main()
{
   f<int>(B<int, int>());
}

This selects #2 in Clang and GCC, as expected, but MSVC rejects the call as ambiguous; no idea why.


The partial ordering algorithm as described in the standard speaks of synthesizing a unique type, value, or class template in order to generate the arguments. Clang manages that by... not synthesizing anything. It just uses the original forms of the dependent types (as declared) and matches them both ways. This makes sense, as substituting the synthesized types doesn't add any new information. It can't change the forms of the A types, since there's generally no way to tell what concrete types the substituted forms could resolve to. The synthesized types are unknown, which makes them pretty similar to template parameters.

When encountering a P that is a non-deduced context, Clang's template argument deduction algorithm simply skips it, by returning "success" for that particular step. This happens not only during partial ordering, but for all types of deductions, and not just at the top level in a function parameter list, but recursively whenever a non-deduced context is encountered in the form of a compound type. For some reason, I found that surprising the first time I saw it. Thinking about it, it does, of course, make sense, and is according to the standard ([...] does not participate in type deduction [...] in [14.8.2.5p4]).

This is consistent with Richard Corden's comments to his answer, but I had to actually see the compiler code to understand all the implications (not a fault of his answer, but rather of my own - programmer thinking in code and all that).

I've included some more information about Clang's implementation in this answer.

Community
  • 1
  • 1
bogdan
  • 9,229
  • 2
  • 33
  • 48
  • 1
    Excellent. As always. +1 – Columbo Aug 15 '15 at 15:33
  • 1
    It just game to my attention that I upvoted and bountied this answer but never actually accepted it. Woops? Anyway, if you're around, I got [another one](http://stackoverflow.com/q/42416993/2069064). – Barry Feb 23 '17 at 13:40
  • @bogdan your answer is very wonderful,but *the third bullet* you added is not rigorous,It may be make misunderstand,such as `void test(T,typename U::b)`,Does it mean just `T` is deduced successfully,then the duduction will be successfully(except the ones that appear only in non-deduced contexts)?Accroding to the context you said and test some codes by the gcc,I think what you actually want to express is that ... if and only if the template paramaters are only appear in the non-deduced contexts,the deduction will be defeat,isn't it? – xmh0511 Feb 13 '20 at 06:53
  • 1
    @jackX That bullet has to be taken together with the second bullet from the modified [p10] below (all params used in the types in `G` need to have values). In other words, the bullets from the first paragraph specify conditions for `P/A` pairs taken separately, while the bullets in [p10] specify conditions that apply globally, taking the template `G` as a whole. Does that answer your question? If you want to discuss your examples, I think it would be a good idea to open a chat room and link to it from here. – bogdan Feb 13 '20 at 11:53
  • @bogdan thanks for your answer,In short ," if and only if the template paramaters are only appear in the non-deduced contexts,the deduction will be defea",the corresponding example is that `template void f(B) { std::cout << "#1\n"; } template void f(B::a>) { std::cout << "#2\n"; }`,Does my understanding right? – xmh0511 Feb 13 '20 at 14:42
  • 1
    @jackX I don't agree with the "only if"; there are many cases where template parameters appear in deduced, or both deduced and non-deduced, contexts, and deduction still fails. Otherwise, yes, in your example, deduction with #1 as argument and #2 as parameter fails because, at the very end, `T` remains without a value. However, deduction at the level of individual `P/A` pairs is considered successful for the second pair, with `typename A::a` as `P`. In general (not in this example), this allows `T` to be potentially deduced from another `P/A` pair and have a value in the end. – bogdan Feb 13 '20 at 18:49
  • @bogdan So,let me summarize our comments,for a single `P/A` pair ,if the `P` is non-deduced context,whatever the template parameters are only appear in it or not only appear in it ,it always can be deduced(in other word,for any non-deduced context,it can be duduced),then accroding to [p50] you modified,if any template parameters remain without values,then the result of template `G` is deduced defeat,Do you agree with me – xmh0511 Feb 14 '20 at 04:15
  • @bogdan and another question is that why the third bullet is "except the ones that appear only..." rather than "except the ones that appear in non-deduced context",since the third bullet need to be taken together with [p10] you modified,why need to stress the "only" ? – xmh0511 Feb 14 '20 at 05:52
  • 1
    @jackX To your first question, I think the answer is "yes" if I understood the gist of it correctly. I wouldn't say "for any non-deduced context, it can be deduced", that's a bit self-contradictory; for a `P/A` pair, *deduction is considered successful* even though some parameters appearing in `P` may remain without a value because they only appear in non-deduced contexts within that `P`. The reason for this explicit rule is that the wording uses this successful/unsuccessful result for each individual pair, which is different from "normal" deduction ([temp.deduct.type] and friends), where... – bogdan Feb 14 '20 at 09:26
  • 1
    ... non-deduced contexts simply don't participate in deduction and we only care about the final overall result. [p10] then comes along at the end and says just that. To your second question, the reason for the "only" is that a parameter can appear in both deduced and non-deduced contexts within the same `P`; for example, `B::a>`. – bogdan Feb 14 '20 at 09:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207829/discussion-between-bogdan-and-jack-x). – bogdan Feb 14 '20 at 09:27
  • @bogdan Some new questions int chat room – xmh0511 Feb 15 '20 at 07:05
5

I believe the key is with the following statement:

The second argument is a non-deduced context - so wouldn't that deduction succeed iff UniqueA were convertible to identity::type?

Type deduction does not perform checking of "conversions". Those checks take place using the real explicit and deduced arguments as part of overload resolution.

This is my summary of the steps that are taken to select the function template to call (all references taken from N3937, ~ C++ '14):

  1. Explicit arguments are replaced and the resulting function type checked that it is valid. (14.8.2/2)
  2. Type deduction is performed and the resulting deduced arguments are replaced. Again the resulting type must be valid. (14.8.2/5)
  3. The function templates that succeeded in Steps 1 and 2 are specialized and included in the overload set for overload resolution. (14.8.3/1)
  4. Conversion sequences are compared by overload resolution. (13.3.3)
  5. If the conversion sequences of two function specializations are not 'better' the partial ordering algorithm is used to find the more specialized function template. (13.3.3)
  6. The partial ordering algorithm checks only that type deduction succeeds. (14.5.6.2/2)

The compiler already knows by step 4 that both specializations can be called when the real arguments are used. Steps 5 and 6 are being used to determine which of the functions is more specialized.

Richard Corden
  • 21,389
  • 8
  • 58
  • 85
  • So are non-deduced contexts in arguments just skipped completely in (6)? – Barry Jul 22 '15 at 11:43
  • @Barry: Type deduction determines the replacements for the template parameters, with some checking (eg. the types generally must match). If all parameters then have values, then they are substituted into the type to check that the final type is 'valid'. Non-deduced contexts do not contribute to the value of template parameters, but they are used when checking the type. Thing is, I'm not sure you can ever have a synthesized type resulting in an invalid type, ie. that `A::type` is valid but `A::type` is not valid. – Richard Corden Jul 22 '15 at 13:27
  • But we need to determine that the types match in (6), that's why we know the "b" deduction failed. If the non-deduced contexts are still used when checking the type, how would "a" not fail? You'd have to not check `identity::type`? – Barry Jul 22 '15 at 18:59
  • @Barry: [Same comment - minor edit] The deduction fails because T deduces first to `Q1` and then to `identity::type`. They're not seen as the same type. The other way around `T` deduces to `Q2` and as `identity::type` is a non deduced context no deduction takes place. We're left with a single valid value for `T` so deduction succeeds. – Richard Corden Jul 23 '15 at 11:34
  • *Type deduction does not perform checking of "conversions".* - Not *conversions*, but *compatibility* in the sense of [14.8.2.5p1], which is a relatively fuzzy notion, but as far as I can tell can be loosely understood as *the same form as detailed below*, that is, in the text of [14.8.2.5]. Let's not forget we're talking specifically about [14.8.2.4] (deduction during partial ordering), not any other section. In this context, the algorithm clearly does more checks than *only that type deduction succeeds*, as stated in your bullet (6). – bogdan Jul 23 '15 at 11:45
  • For example, if your statement were true, why is `template void f(T, int)` more specialized than `template void f(T, U)`? For the second parameter, deduction from `int` to `U` obviously succeeds, then, by your logic, the other way around, from `Q2` (synthesized for `U`) to `int`, no deduction takes place. As you explained above, *We're left with a single valid value for `T`, so deduction succeeds.* Each overload is at least as specialized as the other, so neither can be picked as more specialized. – bogdan Jul 23 '15 at 11:51
  • @bogdan: The point about single value for `T` only applies because there's just one template parameter `T`. Deduction only succeeds if all template parameters are explicitly specified, have defaults or are deduced. If `U` is not deduced then deduction fails. [Having said this I've just read 14.8.2.4/11 which muddies this a bit] – Richard Corden Jul 23 '15 at 12:04
  • Why would `U` not be deduced? I'm talking about deduction the other way around, with `T, int` as parameters and `Q1, Q2` (synthesized from `T, U`) as arguments. By your logic, that deduction should succeed, but it actually doesn't during partial ordering. – bogdan Jul 23 '15 at 12:11
  • @bogdan: You're correct, my previous comment isn't the full story, especially considering 14.8.2.4/11. In the case of `T, int` I believe the difference is because int is not a non-deduced context (14.8.2.5/5). Type deduction does compares the synthesized type Q2 against int. They don't match and so deduction fails. – Richard Corden Jul 23 '15 at 12:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/84079/discussion-between-richard-corden-and-bogdan). – Richard Corden Jul 23 '15 at 12:49
  • @RichardCorden I somehow completely missed the chat comment! This is an amazing thing to read through thank you. – Barry Jul 31 '15 at 14:46