0

Recently I reported a msvc bug involving a function parameter pack. Also as it turns out here that msvc is actually standard compliant there.

Then when I modified the example to what is shown below I noticed that the modified code also cannot be compiled in msvc but can be compiled in clang and gcc. The code is as follows: Demo link

template<typename T> struct C{};

template<typename T> void f(C<T>)
{

}
template<typename... T> void f(C<T...>)
{

}
int main()
{
    f(C<int>{});  //Should this call succeed? 
}

Note that in the above example we have as the function parameter C<T...> as opposed to just T.... Now, in the above shown example, I am not 100% sure that if it is a msvc issue or the standard disallows the program.

So my question is, is the above shown code example well formed. That is, should the call f(C<int>{}); succeed choosing the first overload void f(C<T>) instead of void f(C<T...>)?

In other words, which compiler is right here.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • 1
    Isn't this really the same as [your other recent question](https://stackoverflow.com/q/72664029/10871073) about parameter packs in templates? – Adrian Mole Jun 18 '22 at 20:10
  • 1
    @AdrianMole it is not the same. – Language Lawyer Jun 18 '22 at 20:48
  • @AdrianMole No, they are not the same. – Jason Jun 19 '22 at 04:43
  • I don't see how they are different. – n. m. could be an AI Jun 19 '22 at 04:48
  • @n.1.8e9-where's-my-sharem. They're different because in this case the function parameter is not a function parameter pack. While in my [old post](https://stackoverflow.com/q/72664029/10871073), the function parameter was a function parameter pack. Note there is a subtle difference between the two as here we have the parameter as `C` instead of just `T...`. – Jason Jun 19 '22 at 04:52
  • @AnoopRana OK, I can see that difference, now. But the arguments as to whether it is or is not well-formed (according to the Standard) are *very* similar. The case without the parameter pack is a more specialized form of that with it: **A** fits **B** but not *vice-versa*. – Adrian Mole Jun 19 '22 at 09:23
  • @AdrianMole Yes, i agree they are similar but not exactly the same. Also, i asked a separate question so that we(the people) can answer independently without having to worry about the other case. That is, to keep to question as concise as possible. Btw, i agree with what you said: *"A fits B but not vice-versa"*. I was thinking of writing that as an answer when i posted my question. But then i decided not to write it as an answer as i want to know exactly which clause(s) from the standard allows/disallows the code in question and hence also the language-lawyer tag. – Jason Jun 19 '22 at 09:29
  • @AdrianMole Moroever i asked this same question(with the same example) to the `std-proposal` group(mailing list) but till now i have not got any concrete answer saying whether this is allowed/disallowed by the standard. I also wrote there that my intuition says that the call `f(C{});` should be allowed choosing the first overload. – Jason Jun 19 '22 at 09:31
  • _there is a subtle difference between the two_ The difference between template and function parameter packs is anything but subtle. – Language Lawyer Jun 19 '22 at 09:45
  • @LanguageLawyer Yes, i agree. The reason i said subtle is that sometimes people(beginners) confuse between `C` to be the same as `C...`. That is, the placement of the ellipsis is important. There was nothing more to it. I was trying to make a point that i thought people missed to notice. – Jason Jun 19 '22 at 09:48
  • _i asked this same question(with the same example) to the std-proposal group(mailing list)_ `std-proposal` is for proposals. For questions/discussions there is `std-discussion`. – Language Lawyer Jun 19 '22 at 09:56
  • @LanguageLawyer Ok, i will ask the same there. Thanks. – Jason Jun 19 '22 at 09:57
  • @LanguageLawyer Just an update: I asked it in `std-discussion` mailing list now. – Jason Jun 19 '22 at 10:08
  • @AnoopRana Are you still waiting for more answers or are unhappy with the existing answer? I had a look at std-discussion and it seems you were given the same answer there as I have provided (in detail) below. – dfrib Jun 27 '22 at 09:29

1 Answers1

1

So my question is, is the above shown code example well formed.

The code is well-formed as the function template overload with a non-variadic template parameter is more specialized than the overload with a variadic template parameter, by partial ordering rules.

MSVC is incorrect to reject it.


[temp.func.order]/1 through /4 tells us that we need to turn to partial ordering

/1 If a function template is overloaded, the use of a function template specialization might be ambiguous [...]. Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers:

  • /1.1 during overload resolution for a call to a function template specialization ([over.match.best]);
  • [...]

/2 Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. [...] If so, the more specialized template is the one chosen by the partial ordering process. If both deductions succeed, the partial ordering selects the more constrained template (if one exists) as determined below.

/3 To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.

/4 Using the transformed function template's function type, perform type deduction against the other template as described in [temp.deduct.partial].

[temp.deduct.partial]

/2 Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. [...]

/4 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.

/8 Using the resulting types P and A, the deduction is then done as described in [temp.deduct.type]. [...] If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.

results in, for the original single function parameter function templates, the following P/A pairs:

    P         A
    --------  ----------
#1: C<T>      C<Unique1...>
#2: C<T...>   C<Unique2>

Typically, when not in the context of partial ordering, deduction would succeed for both these pairs. However, [temp.deduct.type]/9 has a special case for when the deduction is performed as part of partial ordering:

/9 [...] During partial ordering, if Ai was originally a pack expansion:

  • /9.2 otherwise, if Pi is not a pack expansion, template argument deduction fails.

This clause means deduction of #1 above (C<T> from C<Unique1...>) fails, whereas #2 (C<T...> from C<Unique2>) succeeds and C<Unique2> is considered at least as specialized as C<T...>. As per [temp.deduct.partial]/10 the non-variadic function template overload is thus at least as specialized as the variadic template function overload and, in the absense of the vice-versa relationsship, moreover more specialized:

/10 Function template F is at least as specialized as function template G 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. 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.

This leads us back to [temp.func.order]/2 and the more specialized function template is chosen by partial ordering:

/2 [...] The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

Which is also covered by [over.match.best]/2.5

/2 Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

  • [...]
  • /2.5 F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that, [...]
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • _Both for which deduction succeeds, as per [temp.deduct.type]/9_ For `C` → `C`, doesn't https://timsong-cpp.github.io/cppwp/n4861/temp.deduct.type#9.2 apply? – Language Lawyer Jun 20 '22 at 10:16
  • `C` vs. `C` looks equivalent to the `S` case in https://timsong-cpp.github.io/cppwp/n4861/temp.deduct.type#9.example-1 – Language Lawyer Jun 20 '22 at 10:21
  • Yes you're right, I missed the key "During partial ordering, if Ai was originally a pack expansion:" before 9.1 and 9.2, thanks. Will update. – dfrib Jun 20 '22 at 11:37