【问题标题】:Template overload resolution and implicit conversions模板重载解析和隐式转换
【发布时间】:2013-10-28 17:15:05
【问题描述】:

想尝试一下,我最近决定尝试在 C++ 中实现一个模板化的复数类。作为参考,我使用了 C++ 11 标准库实现,我发现该实现与我的实现之间存在差异。它指的是他们如何在他们的类中重载 += 运算符。

我基本上只有一个 += 方法,可以同时处理 Complex<T> += Complex<U>Complex<T> += whatever other type that implicitly converts to T and thus Complex<T>。为了澄清,这里是类声明:

template <typename T>
class Complex {
    public:
        constexpr Complex (T=T(), T=T()); // added for clarity

        template <typename U>
        constexpr Complex (const Complex <U>&);
        template <typename U>
        constexpr Complex (Complex <U>&&);
        template <typename U>
        Complex <T>& operator= (const Complex <U>&);
        template <typename U>
        Complex <T>& operator= (Complex <U>&&);

        Complex& operator+= (const Complex<T>&);
        Complex& operator-= (const Complex<T>&);
        Complex& operator*= (const Complex<T>&); 
        Complex& operator/= (const Complex<T>&); 
        Complex& operator++ (void);
        Complex operator++ (int);
        Complex& operator-- (void);
        Complex operator-- (int);

        std::string to_string (void) const;

        constexpr T real (void);
        constexpr T img (void);
        void real (const T&);
        void img (const T&);

    private:
        T _m_real, _m_img;
        template <typename U>
        friend class Complex;
};

但是在标准库实现中,它们为operator+= 使用了2 个重载,一个采用Complex&lt;U&gt;,另一个采用T。就我测试而言,这两种实现似乎都产生了相同的行为:任何两个复杂类型之间的加法,或者一个复杂类型和另一个隐式转换为复杂内部类型的类型之间的加法。

所以,我的问题是:除了优化临时复合体以及如果所有其他复合体类型都隐式转换为 Complex 时为什么要使用嵌套 template &lt;typename U&gt; 之外,还有什么理由需要单独的 operator+=(T)

【问题讨论】:

  • complex&lt; T &gt; &amp; operator+=(const T &amp; t) 仅将 t 添加到复数的 Re 部分。实际上,t 是一个复数,其中 Im 部分等于0complex&lt; T &gt; &amp; operator-=(const T &amp; t) 也是如此。您可以在complex 标头中亲自查看。
  • 您的标量 ctor 的签名丢失了吗?演员中是否缺少=0
  • 是的,我在编辑中解决了这个问题。它在实现中,没有添加到声明中。

标签: c++ templates overloading implicit-conversion


【解决方案1】:
struct Foo {
  operator int()const {
    return 7;
  }
};`

您将遇到递增Foo 的问题,因为只会调用一个用户定义的转换。

举个例子:

Complex<double> d;
Foo f;
d += f; // fails to compile with your version, compiles with C++ standard one

还有一个区别。如果我们有一个类Bar 有一个operator Complex&lt;double&gt;() const,它将无法使用C++ 标准版本。

使用您的版本,如果 T 恰好是 double,则它可以工作。

简而言之,您的参数可隐式转换为/从特定类型转换与采用该类型的参数不同。只能尝试一种用户定义的转换,因此如果您采用可以转换为 int 的类型,则可以接受 int,并且如果您采用可以从 int 进行的类型,则并非所有类型都可以转换为 @ 987654331@可以接受。

非用户定义的转换不受同样的限制。

【讨论】:

  • 主题错误,很有可能。
  • 考虑到标准库更冗长的重载提供了更明确的行为,您会建议不要使用我的实现吗?
  • @vitiv 我不确定。我发现标准中缺少CanImplicitlyConvertTo&lt;Complex&lt;T&gt;&gt; 重载是有问题的。我发现您的 CanImplicitlyConvertTo&lt;T&gt; 重载有问题。在 C++1y 中,这两者都变得更容易了。希望该标准基于实际的实际用例及其决策...查看Complex 提案的历史!
【解决方案2】:

您要比较的两个接口实际上是:

// 1
Complex<T>& operator+=(const Complex<T>&);

// 2
template <typename X>
complex<T>& operator+=(const complex<X>&);
complex<T>& operator+=(const T&);

两个不同之处在于,在 2 中,采用 complex 的重载是根据参数的类型模板化的,并且有一个显式的重载直接采用 T。由于现有的(假设您添加了缺少的构造函数 [*])转换,这两种方法基本上都允许编译相同的代码,但需要额外的成本。

如果你只想给复数的实部加一个值,第一个设计需要创建一个Complex&lt;T&gt;(假设你有那个构造函数,没有显示),而在第二种情况下不需要创建临时对象。

同样,给定 complex 类型的不同实例化作为参数,隐式转换将在 1 中用于创建一个临时值,然后将其添加,而在第二种方法中,参数将直接绑定而无需暂时的。

虽然这对于基本类型并不重要,但如果用于实例化模板的类型是用户定义的并且构造/复制成本很高,则可能会如此。例如,考虑一个动态分配内存的BigNum 类型。临时会导致内存分配和释放。

[*] 不允许默认构造或带有单个参数的构造。

【讨论】:

  • 忘记在帖子中提及,Complex(T,T) 将 T() 作为虚部的默认参数,因此向我的复合体添加实数不需要显式构造。
  • 话虽如此,如果我明白了它的要点,唯一的主要区别是我的版本具有它创建的临时对象的固有开销,现在我想,它有可能成为对于昂贵的构造对象来说是一个巨大的缺点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
相关资源
最近更新 更多