David Hollman - 1 year ago 84

C++ Question

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()`

`template <class T> operator T&()`

`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());

}

Live Demo

But if references are ignored,

`any1`

`any2`

`f2(any1())`

`f2(any2())`

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());

}

Live Demo

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.

Answer Source

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()`

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!

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.