Reputation: 6296
I'm having trouble understanding the implications of the conversion function template argument deduction rules in the C++ standard. The standard states that ([temp.deduct.conv] clause 1, §14.8.2.3.1 in N4594):
Template argument deduction is done by comparing the return type of the conversion function template (call it P) with the type that is required as the result of the conversion (call it A; see 8.5, 13.3.1.5, and 13.3.1.6 for the determination of that type) as described in 14.8.2.5.
where 14.8.2.5 ([temp.deduct.type]) is the section that describes general template argument deduction (though the most common case, function call template argument deduction [temp.deduct.call], no longer seems to point there; did it ever?). The next clause is what confuses me, though (clause 2):
If P is a reference type, the type referred to by P is used in place of P for type deduction and for any further references to or transformations of P in the remainder of this section.
To me, this seems to imply that template <class T> operator T()
and template <class T> operator T&()
are the same (and specifying both would result in an ambiguity). But that isn't the case in any compiler I've used! For instance:
struct any1 { template <typename T> operator T() { } };
struct any2 { template <typename T> operator T&() { } };
void f1(int) { }
void f2(int&) { }
void f3(int const&) { }
int main() {
f1(any1());
// f2(any1()); compile time error
f3(any1());
f1(any2());
f2(any2());
f3(any2());
}
But if references are ignored, any1
and any2
should have the same behavior, right? Clearly they don't, since f2(any1())
doesn't compile with either gcc or clang, while f2(any2())
compiles fine with both.
The next clause (clause 3, particularly 3.3) confuses things even further:
If A is not a reference type: [...] If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction.
This, along with clause 2 about the removal of references, would seem to imply that the following code should not compile because of an ambiguity:
struct any3 {
template <typename T> operator T&() { }
template <typename T> operator T const&() { }
};
void f1(int) { }
int main() {
f1(any3());
}
And yet this works fine with both gcc and clang.
What am I missing?
I should clarify that the way the clang and gcc compilers handle this is exactly what I would expect from a general (relatively advanced) understanding of C++. Some commenters have asked for clarification on what my confusion is (and, implicitly, why I should care). My confusion here is entirely related to trying to understand the implications of the standard. I need a clear understanding of this because I am submitting a paper with code that relies heavily on this working and on my use of it being standards-compliant.
Upvotes: 3
Views: 550
Reputation: 303027
The key point you're missing is that overload resolution still has to happen. Template deduction isn't the end of the story. Addressing both of your examples separately:
To me, this seems to imply that
template <class T> operator T()
andtemplate <class T> operator T&()
are the same (and specifying both would result in an ambiguity). But that isn't the case in any compiler I've used!
The text you cite indicates that deduction of T
is the same for both conversion operators, this is true. But the operators themselves are not the same. You have to additionally consider the rules for binding to references, which are enumerated in [dcl.init.ref]. The section is too long to concisely copy, but the reason that this is an error
f2(any1()); // error
is the same reason that f2(1)
is an error: you can't bind an lvalue reference to non-const
to an rvalue. As a result, even having both operators isn't in of itself ambiguous:
struct X {
template <class T> operator T(); // #1
template <class T> operator T&(); // #2
};
f1(X{}); // error: ambiguous
f2(X{}); // ok! #1 is not viable, calls #2
f3(X{}); // ok! #2 is preferred (per [dcl.init.ref]/5.1.2)
And yet this works fine with both gcc and clang.
struct any3 { template <typename T> operator T&(); // #3 template <typename T> operator T const&() // #4 }; void f1(int) { } int main() { f1(any3()); }
This is an interesting scenario as far as compilers go, because gcc has a bug here. Both candidates should be valid (gcc doesn't consider #4 valid due to 61663). None of the tiebreakers apply on determining best viable candidate, so in this case we have to fall back to [temp.deduct.partial] to determine which candidate is more specializated... which, in this case, is #4.
Upvotes: 4
Reputation: 72311
Type deduction is a separate step from overload resolution and semantic checking.
struct any1 { template <typename T> operator T() { } };
struct any2 { template <typename T> operator T&() { } };
void f1(int) { }
void f2(int&) { }
void f3(int const&) { }
int main() {
f1(any1());
// f2(any1()); compile time error
f3(any1());
f1(any2());
f2(any2());
f3(any2());
}
Here f2(any1())
and f2(any2())
do behave identically for type deduction. Both deduce T=int
. But then that T
is substituted into the original declaration to get member specializations any1::operator int()
and any2::operator int&()
. f2(any1().operator int())
is a semantic error because it attempts to bind a non-const lvalue reference function parameter to an rvalue expression. This makes operator int()
a non-viable function; if any1
had other conversion functions, they could be selected by overload resolution.
struct any3 {
template <typename T> operator T&() { }
template <typename T> operator T const&() { }
};
void f1(int) { }
int main() {
f1(any3());
}
Here again, the two template conversion functions do behave identically for type deduction. Both deduce T=int
. Then that deduction is substituted into the original declarations to get operator int&()
and operator int const&()
. Then overload resolution compares those two. By my reading of Clause 13, they are ambiguous, but gcc chooses operator int&()
and clang chooses operator int const&()
...
Upvotes: 2
Reputation: 32484
Template argument deduction for a function template is just one step in the complex process of overload resolution.
§13.3.1 Candidate functions and argument lists
...
7 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).
Template argument deduction is performed for a given function template as if no other function template exists. Reread the section §14.8.2.3 with that in mind, and you will realize that your questions belong to a different part of the standard.
After template argument deduction is performed for all candidate template functions, the best viable function must be selected according to the rules of §13.3.3. If by this time two or more function template specializations are present in the candidate function list, then the best viable function selection process involves partial ordering rules described in §14.5.6.2 (I think it's this section that contains answers to you questions).
Upvotes: 3