7

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

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

我的代码如下:

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 isconst通过使用标准转换序列(仅包含一个限定转换)隐式转换为。
  2. V<float>(const S<T> & s)被调用并且const V<float>对象生成的时间(我们称之为t)。它已经是 const 限定的,因为它是一个时间值。
  3. a转换为 const 类似于c
  4. operator+(const V<float> & a, const V<float> & b)被调用,导致const V<float>我们可以称之为q的时间类型。
  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>。 1.3 二级标准转换:通过资格转换。V<float>(const S<T> & s)
    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>。 1.3. 最终标准转换:通过资格转换。 V<float>(const S<T> & s)
    V<float>const V<float>
  2. 步骤 2 到 6 与表达式 1 的情况相同。

在阅读了 C++ 标准之后,我:'嘿!也许问题与 13.3.3.1.2.3 有关!其中指出:

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

但事实并非如此,因为资格转换具有完全匹配的排名......

我真的一点头绪都没有...

好吧,不管你有没有答案,谢谢你阅读到这里:)

4

3 回答 3

12

正如 Edric 指出的,在模板参数推导过程中不考虑转换。在这里,您有两个上下文可以从参数的类型推导出模板形参 T:

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

但是您尝试使用V<float>左侧的 a 和右侧的 S 来调用此函数模板。模板参数推导导致左侧的 T=float 并且您将在右侧得到一个错误,因为没有 T 所以V<T>equals S<T>。这符合模板参数推导失败的条件,模板被简单地忽略。

如果您想允许转换,您的 operator+ 不应该是模板。有以下技巧:您可以在 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>. 右侧也正确转换为 a 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 没有矛盾。

于 2010-10-08T06:37:09.017 回答
4

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

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),这也不会编译。

于 2010-10-08T06:19:47.250 回答
0

只是一个猜测,但也许编译器在试图弄清楚如何在表达式 2 中添加 a + c 时无法区分从 V->S 或从 S->V 的转换。您假设编译器将足够聪明选择一个允许编译由于其余可用函数而继续进行的编译器,但编译器可能不是“预读”(可以这么说),并且在尝试查找之前对上转换的歧义感到困惑'+' 运算符。

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

于 2010-10-08T06:18:45.317 回答