我问的最后一个问题是我试图理解另一件事时偶然发现了……我也无法理解(不是我的一天)。

这是一个很长的问题声明,但至少我希望这个问题可能对许多人而言不仅有用,而不仅仅是我。

我拥有的代码如下:

template <typename T> class V;
template <typename T> class S;

template <typename T>
class V
{
public:
 T x;

 explicit V(const T & _x)
 :x(_x){}

 V(const S<T> & s)
 :x(s.x){}
};

template <typename T>
class S
{
public:
 T &x;

 explicit S(V<T> & v)
 :x(v.x)
 {}
};

template <typename T>
V<T> operator+(const V<T> & a, const V<T> & b)
{
 return V<T>(a.x + b.x);
}

int main()
{
 V<float> a(1);
 V<float> b(2);
 S<float> c( b );

 b = a + V<float>(c); // 1 -- compiles
 b = a + c;           // 2 -- fails
 b = c;               // 3 -- compiles

 return 0;
}

表达式1和3完美地工作,而表达式2不编译。

如果我正确理解,会发生的事情是:

表达1

  1. C 是隐式转换为 const 通过使用标准转换序列(仅在一个 资格转换).
  2. V<float>(const S<T> & s) 被称为和暂时的 const V<float> 生成的对象(让我们称呼它 t)。它已经是合格的,因为它是时间值。
  3. 一种 被转换为const类似 C.
  4. operator+(const V<float> & a, const V<float> & b) 称为,导致类型的时间 const V<float> 我们可以打电话 .
  5. 默认值 V<float>::operator=(const & V<float>) 叫做。

我还可以吗?如果我犯了最微妙的错误,请告诉我,因为我试图对隐性铸造的理解尽可能深入了解...

表达3

  1. C 被转换为 V<float>. 。为此,我们有一个用户定义的转换序列:
    1.1。第一个标准转换: S<float>const S<float> 通过资格转换。
    1.2。用户定义的转换: const S<float>V<float> 通过 V<float>(const S<T> & s) 构造函数。
    1.3第二个标准转换: V<float>const V<float> 通过资格转换。
  2. 默认值 V<float>::operator=(const & V<float>) 叫做。

表达2?

我不明白的是为什么第二个表达式存在问题。为什么不可能以下序列?

  1. C 被转换为 V<float>. 。为此,我们有一个用户定义的转换序列:
    1.1。初始标准转换: S<float>const S<float> 通过资格转换。
    1.2。用户定义的转换: const S<float>V<float> 通过 V<float>(const S<T> & s) 构造函数。
    1.3。最终标准转换: V<float>const V<float> 通过资格转换。
  2. 步骤2至6与表达1相同。

阅读了C ++标准后,我:“嘿!也许问题必须与13.3.3.1.2.3!'哪个说明:

如果用户定义的转换由模板转换函数指定,则第二个标准转换序列必须具有精确的匹配等级。

但事实并非如此,因为资格转换具有确切的匹配等级...

我真的不知道...

好吧,无论您是否有答案,感谢您在这里阅读:)

有帮助吗?

解决方案

正如Edric所指出的那样,在模板参数扣除过程中未考虑转换。在这里,您有两个上下文,可以从参数的类型中推导模板参数t:

template<class T>
v<T> operator+(V<T> const&, V<T> const&);
               ~~~~~~~~~~~  ~~~~~~~~~~~~

但是您尝试使用一个 V<float> 在左侧,右侧为s。模板参数扣除t =左侧的float中的结果,右侧您会遇到一个错误,因为没有T V<T> 等于 S<T>. 。这有资格作为模板参数扣除失败,模板被简单地忽略。

如果要允许转换,您的操作员+不应是模板。有以下技巧:您可以将其定义为v:v:

template<class T>
class V
{
public:
   V();
   V(S<T> const&); // <-- note: no explicit keyword here

   friend V<T> operator+(V<T> const& lhs, V<T> const& rhs) {
      ...
   }
};

这样,操作员不再是模板。因此,不需要模板参数扣除,您的调用应起作用。通过ADL(参数依赖查找)找到操作员,因为左侧是一个 V<float>. 。右侧正确转换为 V<float> 也是。

也可以禁用特定参数的模板参数扣除。例如:

template<class T>
struct id {typedef T type;};

template<class T>
T clip(
   typename id<T>::type min,
   T value,
   typename id<T>::type max )
{
   if (value<min) value=min;
   if (value>max) value=max;
   return value;
}

int main() {
   double x = 3.14;
   double y = clip(1,x,3); // works, T=double
}

即使第一个和最后一个参数的类型是int,在模板参数扣除过程中也没有考虑它们,因为 id<T>::type 不是所谓的 *可推论上下文。因此,仅根据第二个参数推导t,该论点导致t = double而没有矛盾。

其他提示

考虑模板匹配时,不使用隐式转换。因此,在以下简单示例中:

template < typename T >
void foo( T t1, T t2 ) { /* do stuff */ }

int main( int argc, char ** argv ) {
    foo( 1, 1.0 );
    return 0;
}

即使可以将任何一个参数隐式转换为其他类型(int <-> double),也不会编译。

只是一个猜测,但也许编译器在试图弄清楚如何在表达式中添加a + c时,编译器无法区分从v-> s或s-> v区分。选择允许编译由于其余功能进行编译的一个,但是编译器可能不是“提前阅读”(可以说),并且在尝试查找之前与上转换的歧义感到困惑“+”运算符。

当然,如果您添加了汇编错误,它也可能有助于澄清问题...

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top