简体   繁体   中英

Why and how do the C++ implicit conversion rules distinguish templated conversion functions from non-templated?

I'm trying to understand the implicit conversion rules in C++, and why the two implicit conversions in the following reduced case differ:

// A templated struct.
template <typename T>
struct A {};

// A templated struct that can be constructed from anything that can be
// converted to A<T>. In reality the reason the constructor is templated
// (rather than just accepting A<T>) is because there are other constructors
// that should be preferred when both would be acceptable.
template <typename T>
struct B {
  template <typename U,
            typename = std::enable_if_t<std::is_convertible_v<U, A<T>>>>
  B(U&& u);
};

// A struct that cna be implicitly converted to A<T> or B<T>.
struct C {
  template <typename T>
  operator A<T> ();

  template <typename T>
  operator B<T> ();
};

// Another struct that can be implicitly converted to A or B, but this time only
// a specific instantiation of those templates.
struct D {
  operator A<int> ();
  operator B<int> ();
};

// A function that attempts to implicitly convert from both C and D to B<int>.
void Foo() {
  B<int> b_from_c = C{};
  B<int> b_from_d = D{};
}

When I compile this with clang, it complains about the b_from_c initialization being ambiguous:

foo.cc:45:10: error: conversion from 'C' to 'B<int>' is ambiguous
  B<int> b_from_c = C{};
         ^          ~~~
foo.cc:24:3: note: candidate constructor [with U = C, $1 = void]
  B(U&& u);
  ^
foo.cc:33:3: note: candidate function [with T = int]
  operator B<T> ();
  ^

This totally makes sense to me: there are two paths to convert from C to B<int> .

But the part that puzzles me is why clang doesn't complain about the b_from_d initialization. The only difference between the two is that the problematic one uses a templated conversion function and the accepted one doesn't. I assume this has something to do with ranking in the implicit conversion rules or the overload selection rules, but I can't quite put it together and also if anything I would have expected b_from_d to be rejected and b_from_c to be accepted.

Can someone help me understand, ideally with citations of the standard, why one of these is ambiguous and the other isn't?

I'm not 100% sure, but I'd say it's point 4 from Best viable function :

F1 is determined to be a better function than F2...
...
4. or, if not that, F1 is a non-template function while F2 is a template specialization

In case of C , both the conversion function in C and the converting constructor B are template specializations and are equally viable.

In case of D , the conversion function in D is a non-template function and so is better than the converting constructor B .

Quoted text is from the C++20 standard, but links are to N4861.

[dcl.init.general]/17.6.3 explains how such a copy-initialization is done:

Otherwise (ie, for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 12.4.2.5, and the best one is chosen through overload resolution (12.4). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call is a prvalue of the cv-unqualified version of the destination type whose result object is initialized by the constructor. The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.

According to 12.4.2.5 ( 12.4.1.4 in N4861 )

[...] Assuming that " cv1 T " is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

  • The converting constructors (11.4.8.2) of T are candidate functions.
  • When the type of the initializer expression is a class type " cv S ", the non-explicit conversion functions of S and its base classes are considered. [...] Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. A call to a conversion function returning "reference to X " is a glvalue of type X, and such a conversion function is therefore considered to yield X for this process of selecting candidate functions.

In both cases, the argument list has one argument, which is the initializer expression.

The first bullet implies that the constructor B::B<D>(D&&) is a candidate function, which we'll call F2 . The second bullet implies that D::operator B<int>() is a candidate function, which we'll call F1 .

The first step to determine whether either F1 or F2 is better than the other is to determine the implicit conversion sequences involved ( [over.match.best.general]/2 ([over.match.best]/2 in N4861) ). To call F1 , the implicit conversion sequence is from D{} to the implicit object parameter of D::operator B<int> (which is of type D& ). This is an identity conversion ( [over.ics.ref]/1 ). To call F2 , the implicit conversion sequence is from D{} to the constructor's parameter type, D&& . This is also an identity conversion for the same reason.

Normally binding a D rvalue to an rvalue reference would be considered a better implicit conversion sequence than binding it to an lvalue reference; [over.ics.rank]/3.2.3 . However, that rule is inapplicable in cases where either of the two bindings is to the implicit object parameter of a function that was declared without a ref-qualifier (as D::operator B<int>() was). The result is that neither of the two implicit conversion sequences is better than the other.

We then have to go through the tiebreaker rules in [over.match.best.general]/2 ([over.match.best]/2 in N4861) . We can see that bullet 2.2 does not apply to our case since our context is [over.match.copy], and not [over.match.conv] nor [over.match.ref]. Similarly bullet 2.3 does not apply. This means 2.4 controls: F1 is not a function template specialization, and F2 is. The overload resolution succeeds, selecting F1 .

In the case of b_from_c , none of the tiebreaker rules apply, and the overload resolution is ambiguous.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM