static_cast not working on precedence as expected
Asked Answered
M

1

9
#include <iostream>
#include <cstdint>

template<int T> void foo()
{
  std::cout << "a" << std::endl;
}

template<uint8_t T> void foo()
{
  std::cout << "b" << std::endl;
}

int main()
{
  foo<static_cast<uint8_t>(42)> ();
  foo<static_cast<int>(42)>();
  return(0);
}

any idea why this isn't working as expected ?

My gcc 4.8.1 is complaining about an ambiguous call, but the static_cast isn't supposed to "fix" the precedence rule in cases like this one where you have 2 types with the same precedence ?

Mammillate answered 26/6, 2013 at 23:50 Comment(14)
I want to say this isn't working because uint8_t is in fact a typedef of int (making it essentially the same type) in this case, whereas static_cast is normally used to resolve precedence rules concerning implicit typecasts and the like. Could be wrong on this one, though.Eteocles
@SilvioMayolo feel free to try with other types with the same precedence of an int, it still doesn't work for me ...Mammillate
I apologize for my complete ignorance: can somebody redirect me to some documentation about what is up here? How are this new kind of templates called? What do they serve for?Denationalize
@Denationalize This isn't new. See $14.1/4 in the C++11 standard for information on non-type template parameters. I don't have the C++03 standard handy but it's in there as well.Yiddish
@Antonio: What kind of new template?Photograph
@MooingDuck Well, anything that doesn't compile if you don't add the -std=c++11 flag in your compiler line... In the old time this template<uint8_t T> didn't exist...Denationalize
@Denationalize uint8_t is a typedef defined in cstdint, a header added in C++11. No new syntax there though.Eteocles
@Denationalize I obviously added that flag, by the way this doesn't compile even with another type with the same precedence like unsigned int, it's not a problem about the new fixed size integers types.Mammillate
@Denationalize It's in the same section ($14.1/4) in the C++03 standard... A non-type template-parameter shall have one of the following (optionally cv-qualified) types: — integral or enumeration type, — pointer to object or pointer to function, — reference to object or reference to function, — pointer to member. C++11 changes the wording for references__"— lvalue reference to object or lvalue reference to function"_ and adds "— std::nullptr_t".Yiddish
Argh, I was tricked by cstdint inclusion, which required me to add the compiler flag, for uint8_t I usually use <stdint.h>. Yeah, now I am back in the game. All my apologies.Denationalize
@Kaz: Er, non-type arguments are perfectly normal. Maybe overloading on them is not, sure, but explicit is the only way to go with non-type.Gleam
@Mammillate Why on earth did you call your numeric parameter T?!? I have never seen a variable/constant being named T, always types. You tricked me! :)Denationalize
@Mammillate I have seen in your comments you focus a lot on the fact that you used a static_cast, but this is a more general problem, the code doesn't work even if you pass constants in which the type is undoubtedly well defined. See thisDenationalize
I still can't see a 100% convincing answer ...Mammillate
S
5

You would think that the compiler, when resolving overloaded function templates, tries to figure out which of the templates matches the given arguments better. Based on that assumption, a template with a uint8_t should match a function call with a uint8_t argument better than a template for int.

But that's not how template overload resolution works. Template overload resolution (§14.5.6.2) is different from ordinary function overload resolution (§13.3). It first establishes candidate templates and then, rather than trying to check how well each matches the given arguments, it simply establishes which of the two (or more) candidate templates is the most specialized.

Note that this is a matter between the candidate templates only. It does not take into account the given arguments of the function call. (Those are taken into account for type deduction, which is only part of the procedure that establishes the candidate set of templates.)

So it checks whether uint8_t is more specialized than int or vice versa (in general – not with respect to the given arguments of the function call at hand). It does this, basically, by checking whether any given uint8_t argument could (in theory) be used to fill an int parameter without non-standard conversions, and vice versa. This is the case (in both directions), so neither template is more specialized than the other. Hence the ambiguity cannot be resolved.


The relevant sections of the Standard are as follows.

First, §13.3.3 establishes that when two functions templates (as opposed to two ordinary functions, or one function and one template) compete for a function call, the template overload mechanism is used to select the best:

[...] 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

[...] — 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 14.5.6.2.

Then, §14.5.6.2 is very long, but the most relevant parts are:

(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. 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.

(3) To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (14.5.3) 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 14.8.2.4.

So the idea here is: Take the uint8_t template and transform it by replacing the uint8_t parameter with an actual, synthesized value (I guess that value could be taken from the actual function call, but the Standard doesn't say that). Then use the type deduction process to check if the transformed template, taken as a function call, would "match" the other template (i.e. the int template), i.e. if the int parameter of the other template could be deduced without non-standard conversions. The answer is yes, it could.

Then go the other way, take the int template, synthesize a value and try if this "matches" the uint8_t template, i.e. if the uint8_t parameter could be deduced without non-standard conversions. The answer is yes again.

If this works in only one direction, one of the two templates must be more specialized than the other and is chosen to resolve the ambiguity. If it works both ways (as in your case), the ambiguity cannot be resolved.

Note. The entire procedure is in fact more complicated, and its description in the Standard very long, mainly for the following reasons:

  • The type deduction process itself is complicated. It does allow for certain implicit conversions (basically, standard-conversions including some related to cv-qualifiers);
  • It has a number of special arbitration rules for the case when one candidate parameter is a const-reference and the other in a non-const reference, and some similar cases;
  • Each candidate template may give rise to multiple transformed templates, especially when there is more than one template parameter to be deduced;
  • The presence of default-arguments as well as template parameter packs complicates the situation further.
Stockman answered 27/6, 2013 at 1:36 Comment(8)
@OP Note that we had a similar question yesterday: #17314149 I am still contemplating whether yours (asking "why") should be regarded a duplicate of yesterday's (asking "how to resolve").Stockman
why no one is considering the static cast ? your words are ok considering this code without a static cast, but ... there is a static cast involved here ...Mammillate
@Mammillate The static_cast only changes the type of the given arguments of the function call. But those are not considered when deciding which overload is more specialized. Only the two template are compared -- the given arguments (cast or not) are not taken into account for this decision.Stockman
I'm pretty sure that the static_cast is playing a role in the precedence simply because it's what it should do and it's used in templates signature just like that to do just like what it does in this case. There must be something "special" about this or a bug, I can't be convinced that a cast it's not relevant in a system that involves type deduction mechanisms.Mammillate
@Mammillate I understand your frustration, but there is no doubt that the behaviour is as defined by the Standard. And to clarify: A static cast can be relevant for this. Any cast that performs a non-standard conversion that would not be applied implicitly during a variable initialization will change the type in a way that is relevant for type-deduction, so it will change the set of candidate templates. But in your case the conversion is between two integral types (which can be implicitly converted into each other), and that is just not enough to resolve the ambiguity.Stockman
Type deduction has a lot in common with variable initialization. If you think of something like: uint8_t i = static_cast<uint16_t>(8); This works, although the declared type and the type of the initialization value are explicitly different.Stockman
so i can't solve this ambiguity while keeping this kind of signature for the templates? is that what you are saying ?Mammillate
@Mammillate Yes, I believe there is no direct solution. For solutions, please refer to the almost-duplicate, #17314149. Unfortunately there is no solution that does not require changing the template parameters.Stockman

© 2022 - 2024 — McMap. All rights reserved.