如果模板参数有,则只重载运算符

时间:2015-04-24 19:31:32

标签: c++ operator-overloading c++14

给定具有单个模板参数T的模板类A,是否可以仅重载A中可用于类型T的运算符?例如:

template <typename T>
class A
{
public:
    #if hasOperator(T, +=)
    T& operator +=(const T &rhs)
    {
        mValue += rhs;
        return mValue;
    }
    #endif

private:
    T mValue;
}


int main()
{
    A<int> a;
    a += 8; //+= will forward to the += for the int

    struct Test {  /*no operators defined*/ };
    A<Test> b; //+= is not implemented since Test does not implement +=
}

我正在编写一个通用的包装类,需要的行为与模板类型完全相同。因此,如果T有operator + =,A将(在编译时)重载+ =相应。是的,我可以继续实现A中的每个运算符,但是当T没有某个运算符时编译器会出错。起初我虽然模板专业化可能是答案,但这需要针对每种类型的专业化。虽然这可以工作并且需要大量打字,但它不会因为A需要使用任何类型(不仅仅是专门的)。

3 个答案:

答案 0 :(得分:4)

使用表达式SFINAE从重载决议集中删除operator+,除非T定义operator+

template <typename T>
class A
{
private:
    T mValue;
public:
    template<typename U=T>
    auto operator +=(const U &rhs)
        -> decltype(mValue += rhs)
    {
        mValue += rhs;
        return mValue;
    }
};

Live demo

答案 1 :(得分:2)

我将提供3种解决方案,降低复杂性和实用性。最后一个解决方案是最简单,最简单的。

一个小的,如果有用的元编程库:

template<class...>struct types{using type=types;};
namespace details {
  template<template<class...>class Z, class types, class=void>
  struct can_apply : std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z,types<Ts...>,std::void_t<Z<Ts...>>> :
    std::true_type
  {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,types<Ts...>>;

+=的结果的特征:

template<class Lhs, class Rhs>
using plus_equal_result = decltype(std::declval<Lhs>()+=std::declval<Rhs>());

template<class Lhs, class Rhs>
using can_plus_equal = can_apply< plus_equal_result, Lhs, Rhs >;
template<class T>
using can_self_plus_equal = can_plus_equal< T&, T const& >;

它给了我们一些很好的特性,它们返回真实类型或假类型,具体取决于+=是否有效。

template<class A, class T, bool b = can_self_plus_equal<T>{}>
struct A_maybe_plus_equal {};
template<class A, class T>
struct A_maybe_plus_equal<A, T, true> {
  A& self() { return *static_cast<A*>(this); }
  A& operator+=( T && t )
  {
    self().mResult += std::move(t);
    return self();
  }
  template<class U>
  std::enable_if_t<can_plus_equal<T&,U>{},A&> operator+=( U && u )
  {
    self().mResult += std::forward<U>(u);
    return self();
  }
};

如果我们传递了真的话,它会给我们+=

template <class T>
class A:
  public A_maybe_plus_equal<A<T>, T>
{
  friend class A_maybe_plus_equal<A<T>, T>;
public:
  // nothing needed
private:
  T mValue;
};

为您提供+=重载,当{当前const T&T&&时,右侧会有U&&T& += T const&{}一个有效的表达。

这是&#34;完美&#34;解决方案,但它很复杂。

请注意,每个操作员都可以单独完成,因此您不会有专业化的组合爆炸。

现在,有一个更简单的选择。它的缺点是它不支持template <typename T> class A { public: template<class U> auto operator +=(U&&rhs) -> decltype( (std::declval<T&>()+=std::declval<U&&>()),void(),A& ) // or std::enable_if_t<can_plus_equal<T&,U>{},A&> { mValue += std::forward<U>(rhs); return *this; } private: T mValue; }; 基于右侧的构造,并且根据标准的一些读数,它是非法的。

然而,它仍然是SFINAE友好的:

{}

这可以折叠到上面的选项中,并提供T const&和完美的转发语法。如果您有template完美转发器,我发现可以删除+=

这是技术上未定义的行为的原因是标准要求所有模板函数至少有一组参数可以使它们的主体能够编译。在给定的类实例中,上面的模板plus_equal可能没有这样的类型参数集,这会使您的程序格式错误而不需要诊断(即UB)。

除非被调用,否则还有另一个规则,即模板类的成员函数不会被实例化。有些人认为这另一条规则取代了我在上一段中提到的规则。

另一个论点是,只要有一些模板参数混合到封闭类和模板方法本身,导致它可以实例化,该方法可能是合法的。我猜这是标准委员会的意图,但我不知道如何阅读标准来获得这个结果。

此参数也适用于答案#1中的{}函数。这种实现不必那么简单。此外,#1提供基于+=的{​​{1}}语法,这是使用它的一个实际原因。这个问题 - 该程序在技术上是不正确的 - 是学术上的,因为我使用的所有编译器都没有这个结构的问题。

这一段上面的第三段给了我们最后的选择。什么都不做。

template <typename T>
class A {
public:
  A& operator +=(const T &rhs) {
    mValue += rhs;
    return *this;
  }
private:
  T mValue;
};

这意味着您无法对+=无效的SFINAE测试进行测试,但只要您不打电话+=它就会工作&#34;。例如,这就是vector&#39; s operator<的工作原理。这是一个较低的质量&#34;解决方案,标准库中的这种情况往往会随着时间的推移得到修复。

然而,作为第一关,最后的选择通常是最好的。只有当你期望SFINAE要求是值得的上述箍时。

最终,C ++ 1z引入了概念。我相信概念会使这个问题变得更容易,因为根据封闭类的类型参数消除重载是std中长期存在的问题。

答案 2 :(得分:1)

你实际上不需要做任何事情。模板类的各个成员函数在使用之前不会被实例化。你说:

  

但是当T没有某个运算符时编译器会出错。

但是当A<T>没有错误时,它是否比错误更清楚?如果你有:

template <typename T>
class A
{
public:
    A& operator +=(const T &rhs)
    {
        mValue += rhs;
        return *this;
    }

    A& operator-=(const T &rhs)
    {
        mValue -= rhs;
        return *this;
    }

    // etc

private:
    T mValue;
};

然后这将起作用:

int main() {
    A<int> a;
    a += 8; //+= will forward to the += for the int

    struct Test {
        Test& operator-=(const Test& ) { return *this; }
    };

    A<Test> b;
    b -= Test{};   // totally fine
    b += Test{};   // error: no match for += 
                   // (operand types are 'main()::Test' and 'const main()::Test')
}