c ++模板返回类型取决于模板参数?

时间:2012-07-01 11:12:52

标签: c++ templates generic-programming

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

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

// 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

到目前为止一切顺利。问题是当我定义第二个运算符(Vector / Scalar)时,这个运算符是如此通用,以至于编译器也想将它用于向量/向量除法。 但它失败了,因为Lhs()/ Rhs()带有:

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

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

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

1 个答案:

答案 0 :(得分:2)

编译器不希望将Vector / Scalar运算符用于向量/向量除法。它只是想检查一下是否匹配。

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

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<something,other>而不是给出它通常的错误。

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

更新:更详细的解释

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

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

首先考虑Vector / Vector operator/。编译器推断出LhsRhs模板参数都是Length(为简洁起见,我省略了名称空间)。然后,它将它们替换为TV3TypeV3Div的参数,计算TV3TypeV3Div的内部,确定TV3TypeV3Div<Lhs,Rhs>::type也是Length,最后计算operator/的返回类型这是TV3Type<Length>

现在考虑Vector / Scalar operator/。编译器推断LhsLength,但RhsTV3Type<Length>。然后,它将这些类型替换为TV3TypeV3Div的参数,并尝试计算TV3TypeV3Div的内部。这是事情的分歧:没有operator/可以接受LengthTV3Type<Length>。因此无法计算TV3TypeV3Div<Lhs,Rhs>::type。编译器输出错误。

现在考虑声明通用operator/时会发生什么。现在, operator/即可接受LengthTV3Type<Length>! (或者任何其他一对论点,就此而言)。因此编译器愉快地计算TV3TypeV3Div<Lhs,Rhs>::type,然后返回Vector / Scalar 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之外,这应该可以很好地隐藏通用运算符/。