【问题标题】:Overloaded operators & inheritance & templates (A formidable combination)重载运算符 & 继承 & 模板(一个强大的组合)
【发布时间】:2011-08-25 08:54:30
【问题描述】:

大家好。

我正在使用 Boost Units 库编写一些代码,但遇到了问题。

我已经设法从 Boost 代码中抽象出问题,这样您就不会查看大量的 boost 模板元编程。虽然我敢肯定,如果你有这方面的经验,它会有所帮助。这是复制品:

class Base{};
class Derived : public Base
{
public:
  Derived(){}
  Derived(const Base &){}
};

class Q {};
class U
{
public:
  template< typename Y >
  Q operator * (Y)
  {
    Q r;
    return r;
  }
};

Base operator * (U, const Base &)
{
  Base r;
  return r;
}

int main(int argc, char **argv)
{
  Base myBase;
  U myU;
  Base myOtherBase = myU * myBase;
  Derived myDerived;
  Derived myOtherDerived =  myU * myDerived;
  return 0;
}

所以问题(具体)如下:myU * myBase 使用operator * (U, const Base &amp;) 并返回Base 类型,到目前为止一切正常。而myU * myDerived 坚持使用广义U::operator * (Y) 并因此返回Q,这不好,因为我又想要Base

现在,除了BaseDerived 之外的所有类都是boost 库类,所以我不能修改U 的成员。我如何“击败”U::operator * (Y) 进行重载/模板推导/实例化,在这种情况下,以优雅且“一劳永逸”的方式解决。

我正在使用 MSVC++ 2008,以防它与任何人相关。

编辑:在答案中添加了一个可能(很可能)的解决方案

【问题讨论】:

  • +1 用于小型、独立的示例代码。 :)
  • 想到了什么:Boost U 类在操作符上使用 SFINAE 吗?
  • 你刚刚了解到不幸的是继承和模板不能很好地混合:/
  • @Matthieu:这绝对不是真的。它们混合得很好,因为它们解决了正交问题。编译时元编程运行时多态性的组合可以构建出色的代码。
  • @Alexandre C:我同意,您可以解决很大的问题,但是在不同的重载中使用基类和模板参数并让它们按照您的意愿行事可能会导致很多麻烦。当然有解决方案,但肯定不像人们想象的那么直接,因为方法的正交性:) 我认为我解决了这个问题没有太多问题,但是,它确实花了 OP 几天的时间来计算出最小的例子:/

标签: c++ templates inheritance operator-overloading


【解决方案1】:

使用以下内容应该可以解决您的问题

Base myOtherDerived =  myU * (Base&)myDerived;
// or
Base myOtherDerived =  myU * static_cast<Base&>(myDerived);

而不是

Derived myOtherDerived =  myU * myDerived;

不过,这不是我们所说的“干净的解决方案”。 我正在努力寻找更好的方法。

【讨论】:

  • 这种方法的明显问题是切片。也许@EdF 需要一些通过基本引用调用的虚函数? ://
  • 这可能会解决它。然而,所讨论的派生类被大量使用,并且每次强制转换对于客户端代码来说都是不切实际的(由于typedefs,几乎不会意识到派生类甚至有一个基类)。
  • 说实话切片实际上不是问题。 Derived 不包含比 Base 更多的数据,但我从不认为即使在“不会造成伤害”的情况下切片也是一个好主意。
  • @Valkea:转换为Base&amp;(注意参考)解决了切片问题。 ;)
【解决方案2】:

首先,问题是:operator*const Base&amp; 参数总是比模板参数的精确拟合更差,因为从Derived 转换为Base
接下来,解决方案:为每个派生类提供重载的operator*。 :(

【讨论】:

  • @Xeo:我在发布时注意到了这一点,我已将其更改为const Base &amp;,这就是它在真实代码中的样子。它不影响结果。我希望对于每个派生类都有比 operator * 更好的解决方案。
  • @EdF:我至少不知道其他人。即使将 Base 参数作为指针也无济于事,我认为这可能...:/
【解决方案3】:

如果您可以控制运算符的顺序,您可以在相反的方向定义您的 operator*,即

Base operator* (const Base& lhs, U rhs)
{
    Base r;
    return r;
}

现在如果你尝试了

Derived myOtherDerived =  myDerived * myU;

它不适合 U 类中的模板,从而解决 U 中的模板函数覆盖您自己的 operator* 函数的问题。

【讨论】:

  • 问题是,现在你必须myDerived * myUmyBase * myU并且不能改变顺序。我认为问题在于U 提供的重载仍然更合适,因为不需要重新排序参数。
  • 所有操作符都是以两种方式定义的,在我的代码和库中。我省略了这些内容以使示例尽可能简洁,因为就基本语言问题而言,它已经很庞大了。
  • 好吧,如果你刚刚开始,并且没有大量的代码库需要维护,那么值得一试:)
  • 感谢,我已经在这个问题上花了几天时间......直到今天,我才终于设法在没有嵌入模板元编程的英里数的情况下重现它。我当然感觉更接近现在解决方案已简化为当前形式。希望有人会有一个大规模的头脑风暴想法。问题是有很多方法可以几乎修复它。
  • @EdF:或者真的修复它,但在你身边有大量的维护部分,为派生类提供所有重载的operator*。 ://
【解决方案4】:

有趣的问题。模板和继承并不能很好地混合在一起,而且由于您无法修复 U,这无疑是一个有趣的挑战。

我建议胜过超载扣除机制:)

最简单的方法是为每个Derived 类提供重载。显然这是不切实际的。

除非我们可以使用为其编写运算符的辅助类,并在其中混合 typedef 以使其对客户端透明。

template <typename T>
struct BaseT: Base
{
  typedef T Tag;
};

template <typename T>
BaseT<T> operator*(U, BaseT<T> const&) { return BaseT<T>(); }

对于任何BaseT&lt;X&gt;,这应该是首选(作为重载),因为它比建议的通用重载更精确地匹配。

struct DerivedTag {};

typedef BaseT<DerivedTag> Derived;

Tadaaaam :)

而且由于BaseT 是一个类,您实际上可以将其专门用于特定的标记参数,以拥有您想要的成员/其他功能,并且对客户来说感觉完全一样。

http://ideone.com/ZIudh 上有关 Ideone 的完整示例,希望您不会遇到 VS 2008 错误 ;)

struct Base {};

template <typename T>
struct BaseT: Base
{
  typedef T Tag;
};

struct DerivedTag {};
typedef BaseT<DerivedTag> Derived;

class Q {};
class U
{
public:
  template< typename Y >
  Q operator * (Y)
  {
    Q r;
    return r;
  }
};

Base operator * (U, const Base &)
{
  Base r;
  return r;
}

template <typename T>
BaseT<T> operator*(U, BaseT<T> const&) { return BaseT<T>(); }

int main(int argc, char **argv)
{
  Base myBase;
  U myU;
  Base myOtherBase = myU * myBase;
  Derived myDerived;
  Derived myOtherDerived =  myU * myDerived;
  return 0;
}

【讨论】:

    【解决方案5】:

    就像我昨晚放弃尝试解决这个问题一样,答案像砖头一样击中了我。 U::operator*(Base)Q 类型化结果在概念上与operator*(U,Base)Base 类型化结果相同,尽管它们由不同的类型表示。我需要做的就是为Base ( const Q &amp; ) 提供一个接受Q 类型的构造函数。

    class Base
    {
    public:
    Base(){}
    Base(const Q &){}
    };
    

    这使我的示例编译。现在看看我是否真的可以在真实版本中编写所需的构造函数(其中Base 实际上是Base&lt;Q,...&gt;,而我们接受的QQ&lt;Base,...&gt;)。

    不幸的是,提出一个简化的抽象问题示例使得发现这个解决方案变得更加困难,所以我有很大的优势。感谢所有提供想法/ cmets 和答案的人。

    【讨论】:

    • 嗯,这真的能解决问题吗?这实际上是我最初的想法之一,但我认为您无法从 Q 进行任何有用的构造。如果有效,那么恭喜。 :)
    【解决方案6】:

    在 C++03 中使用 SFINAE(boost::is_base_of 来自 Boost.TypeTraits):

    class U {
    public:
        template<typename Y>
        typename boost::enable_if<
            !boost::is_base_of<Base, Y>::value,
            Q
        >::type
        operator*(Y)
        {
            Q r;
            return r;
        }
    };
    

    使用 C++0x 的另一种方法(std::is_base_of 来自 &lt;type_traits&gt;):

    class U {
    public:
        template<
            typename Y,
            typename = typename std::enable_if<
                !std::is_base_of<Base, Y>::value
            >::type
        >
        Q
        operator*(Y)
        {
            Q r;
            return r;
        }
    };
    

    快速测试表明您的示例似乎适用于 SFINAE,但请注意,两个运算符必须是重载才能使其工作:如果 operator*(Y) 是出现在重载解决方案集中的唯一运算符(例如,由于 ADL或者因为 operator*(U, Base const&amp;) 被声明的位置)然后 SFINAE 将使其消失(如预期)但重载决议将在没有候选人的情况下结束。


    正如 Xeo 指出的那样,上述内容无济于事。为了希望救赎自己,这是最后一种可能性:

    在示例中,成员 operator*(Y) 是非常量。然而,如果它是像 operator*(Y) const 这样的 const 风格,那么你可以提供一个更好的非 const 匹配,转发给你的 operator*(U const&amp;, Base const&amp;)。然而,这很脆弱:如果您的代码使用U const&amp;,那么您将偶然发现原始编译错误。

    【讨论】:

    • OP 说除了基类和派生类他不能改变任何东西,因为它来自 Boost。
    猜你喜欢
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    相关资源
    最近更新 更多