【问题标题】:c++ template return type depending on template arguments?c++ 模板返回类型取决于模板参数?
【发布时间】:2012-07-02 03:31:19
【问题描述】:

我已经实现了自己的 SI Unit 类。使用算术运算时,生成的 SI 单位可能会发生变化。例如:(米/秒)/米=1/秒。

好的,现在我还创建了一个简单的 3D 矢量类。这个向量应该是通用的,也可以用于我的 SI Unit 类。所以我实现了一个简单的除法运算符。 见以下代码:

// Determine result type of Lhs / Rhs:
template < class Lhs, class Rhs >
    struct TV3TypeV3Div { typedef BOOST_TYPEOF( Lhs( ) / Rhs( ) ) type; };

// Vector / Vector:
template < class Lhs, class Rhs >
RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type > operator/( const RobotTools::DataTypes::TV3Type< Lhs >& lhs,
                                                                                     const RobotTools::DataTypes::TV3Type< Rhs >& rhs )
{
    // do something useful
    return RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type >( 0, 0, 0 );
}

// Vector / Vector
RobotTools::DataTypes::TV3Type< Tools::DataTypes::Length > vl;
vl / vl;  // Ok this works

在编译期间,正确的返回类型将通过使用 TV3TypeV3Div 结构来确定。这行得通。

现在我想扩展运算符。我还想计算标量类型的向量。所以我写了这个运算符:

// Vector / Scalar
template < class Lhs, class Rhs >
RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type > operator/( const RobotTools::DataTypes::TV3Type< Lhs >& lhs,
                                                                                     const Rhs& rhs )
{
    // do something useful
    return RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Tools::DataTypes::Length >::type >( 0, 0, 0 );
}

// Vector / Scalar
RobotTools::DataTypes::TV3Type< Tools::DataTypes::Length > vl;
Tools::DataTypes::Length sl;
vl / sl;  // Ok nice it works too

到目前为止一切顺利。问题是当我定义第二个运算符(向量/标量)时,这个运算符非常通用,编译器也希望将它用于向量/向量除法。 但它失败了,因为 Lhs( ) / Rhs( ) 有:

Lhs=Tools::DataTypes::Length 和 Rhs=RobotTools::DataTypes::TV3Type

未定义。这是正确的,我理解给定的错误。我不明白的是编译器不使用 Vector/Vector 运算符。

  • 有没有可能给编译器提示使用哪个运算符?
  • 是否有可能重写运算符以满足我的要求?

【问题讨论】:

    标签: c++ templates generic-programming


    【解决方案1】:

    编译器不想使用向量/标量运算符进行向量/向量除法。它只是想检查是否有匹配。

    如果您声明(但未定义)一个完全通用的除法运算符

    template <typename Lhs, typename Rhs>
    struct InvalidDivision {};
    template <typename Lhs, typename Rhs>
    InvalidDivision<Lhs, Rhs> 
    operator/(const Lhs& lhs, const Rhs& rhs); // do not define
    

    那么你的代码应该编译和链接。 Vector/Scalar 重载将被考虑并拒绝,因为 Vector/Vector 是更好的匹配。

    它的缺点是如果你对没有定义除法的东西进行除法,编译器会抱怨InvalidDivision&lt;something,other&gt;而不是给出通常的错误。

    也许这可以通过使用 SFINAE 或其他一些高级魔法来改善。

    更新:更详细的解释

    原始版本的代码发生了什么?

    我们正在尝试为用户定义的类型调用除法运算符。有两个名为 operator/ 的函数,编译器会同时考虑这两个函数并尝试找出哪个更匹配。

    首先考虑向量/向量operator/。编译器推断Lhs 和Rhs 模板参数都是Length(为简洁起见,我省略了命名空间)。然后将它们代入TV3TypeV3Div的参数,计算TV3TypeV3Div的内部,确定TV3TypeV3Div&lt;Lhs,Rhs&gt;::type也是Length,最后计算operator/的返回类型为TV3Type&lt;Length&gt;。

    现在考虑向量/标量operator/。编译器推断Lhs 是Length,但Rhs 是TV3Type&lt;Length&gt;。然后它将这些类型替换为TV3TypeV3Div 的参数,并尝试计算TV3TypeV3Div 的内部。这就是事情破裂的地方:没有operator/可以接受Length和TV3Type&lt;Length&gt;。所以不可能计算TV3TypeV3Div&lt;Lhs,Rhs&gt;::type。编译器输出错误。

    现在考虑当泛型 operator/ 被声明时会发生什么。现在是一个operator/ 可以接受Length 和TV3Type&lt;Length&gt;! (或任何其他一对论点,就此而言)。所以编译器很高兴地计算TV3TypeV3Div&lt;Lhs,Rhs&gt;::type,然后是向量/标量的返回类型operator/。所以现在可以同时考虑 Vector/Vector 和 Vector/Scalar operator/ 重载。

    编译器现在还看到并考虑通用operator/ 用于重载解决方案。所有三个重载都会产生匹配。但 Vector/Vector 重载胜出,因为它比 Vector/Scalar 或泛型 operator/ 更专业,因此更适合。

    更新 2

    应该可以这样做:

    namespace Detail
    {
        template <typename Lhs, typename Rhs>
            struct DoNotUse {};
        template <typename Lhs, typename Rhs>
            DoNotUse<Lhs, Rhs> operator/(const Lhs& lhs, const Rhs& rhs);
        template < class Lhs, class Rhs >
            struct TV3TypeV3Div { typedef BOOST_TYPEOF( Lhs( ) / Rhs( ) ) type; };
    }
    using Detail::TV3TypeV3Div;
    

    这应该很好地向所有人隐藏通用运算符/,但TV3TypeV3Div 除外。

    【讨论】:

    • 我很困惑!这真的有效。我花了一整天的时间来解决这个简单的解决方案:) 你能解释一下吗?我没有看到魔法。
    • 我已经添加了一些解释。
    • 谢谢 n.m.为伟大的解释。与此同时,我在我原来的项目中尝试了你的方法。但它在多个地方失败并出现“InvalidDivision ...”错误代码。我认为更新整个项目并不容易。所以也许我需要一些 SFINAE 魔法也许通过使用 boost ?有什么想法吗?
    • 尝试将TV3TypeV3Div 和InvalidDivision 和InvalidDivision operator/ 放入namespace Detail,然后说using Detail::TV3TypeV3Div。这应该对所有人隐藏通用运算符/。
    • 是的,这最终解决了我的问题。简洁大方!坦克很多!
    猜你喜欢
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-09
    相关资源
    最近更新 更多