不发生隐式转换

时间:2010-10-08 06:06:39

标签: c++ templates implicit-conversion

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

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

我的代码如下:

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. 调用
  3. V<float>(const S<T> & s)并生成const V<float>对象的时间(让我们称之为 t )。它已经是const限定的,因为它是一个时间值。
  4. a c 类似地转换为const。
  5. operator+(const V<float> & a, const V<float> & b)被调用,产生const V<float>类型的时间,我们可以将其称为 q
  6. 调用默认V<float>::operator=(const & V<float>)
  7. 我可以到这儿吗?如果我发出了最微妙的错误,请告诉我,因为我试图尽可能深入了解隐式演员......

    表达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>)
    3. 表达式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的情况相同。
      3. 在阅读C ++标准之后我说:'嘿!或许问题必须与13.3.3.1.2.3!'其中说明:

          

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

        但情况并非如此,因为资格转换具有完全匹配等级......

        我真的不知道......

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

3 个答案:

答案 0 :(得分:12)

正如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:

的类模板中将其定义为内联朋友
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不是所谓的* deducible context`。所以,T只是根据第二个参数推导出来,这导致T = double而没有矛盾。

答案 1 :(得分: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&lt; - &gt; double),也不会编译。

答案 2 :(得分:0)

只是一个猜测,但也许编译器无法区分从V-> S或从S-> V的转换,同时试图弄清楚如何在表达式2中添加+ c。您假设编译器将足够智能选择允许编译继续进行的那个,因为剩下的可用函数,但编译器可能不是“提前阅读”(可以这么说),并且正在混淆上行的模糊性在尝试查找“+”运算符之前进行转换。

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