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

c++ template return type depending on template arguments?

本文关键字:参数 取决于 返回类型 C++      更新时间:2023-10-16

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

好的,现在我还创建了一个简单的 3D 矢量类。这个向量应该是通用的,也可以与我的 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

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

lhs=Tools::D ataTypes::

Length and Rhs=RobotTools::D ataTypes::TV3Type

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

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

编译器不希望使用 Vector/Scalar 运算符进行 Vector/Vector 除法。它只是想检查是否有匹配项。

如果您声明(但不定义)完全泛型除法运算符

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

然后,您的代码应该编译并链接。矢量/标量重载将被考虑并拒绝,因为矢量/矢量是更好的匹配。

它的缺点是,如果你确实划分了没有为它们定义除法的东西,编译器会抱怨InvalidDivision<something,other>而不是给出它通常的错误。

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

更新:更详细的解释

代码的原始版本是怎么回事?

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

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

现在考虑矢量/标量operator/。编译器推断LhsLength,但RhsTV3Type<Length>。然后它把这些类型代入TV3TypeV3Div的参数,并尝试计算TV3TypeV3Div的内部。事情在这里破裂:没有operator/会接受LengthTV3Type<Length>。所以不可能计算TV3TypeV3Div<Lhs,Rhs>::type.编译器输出错误。

现在考虑声明泛型operator/时会发生什么情况。现在一只operator/可以接受LengthTV3Type<Length>!(或任何其他论点,就此而言)。因此,编译器很乐意计算TV3TypeV3Div<Lhs,Rhs>::type,然后计算向量/标量operator/的返回类型。因此,现在可以同时考虑矢量/矢量和矢量/标量operator/重载。

编译器现在还查看并考虑重载解析的泛型operator/。所有三个重载都会产生匹配项。但是向量/向量重载胜出,因为它比向量/标量或泛型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以外的所有人隐藏泛型运算符/。