我将给出 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();
}
};
这给了我们一个+= 如果我们通过了 true。
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;
};
当且仅当T& += T const& 是一个有效的表达式时,它会给你一个+= 重载,它在右侧采用const T& 或T&& 或U&&。
这是“完美”的解决方案,但很复杂。
请注意,每个运算符都可以单独完成,因此您不会出现专业化的组合爆炸。
现在,有一个更简单的选择。它的缺点是它不支持右侧基于{} 的构造,并且在某些标准读数下它是非法的。
不过,它仍然对 SFINAE 友好:
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;
};
这可以折叠到上面的选项中,并给出{} 和完美的转发语法。我发现如果你有一个 template 完美转发器,T const& 可以被删除。
这在技术上是未定义行为的原因是标准要求所有模板函数至少有一组参数,可以使其主体能够编译。在给定的类实例中,上述模板+= 可能没有这样的类型参数集,这会使您的程序格式错误,无需诊断(即UB)。
还有一条规则是模板类的成员函数除非被调用,否则不会被实例化。有人争辩说,这条规则取代了我在上一段中提到的规则。
另一个论点是该方法可能是合法的,只要有一些模板参数混合到封闭类和模板方法本身,导致它可以实例化。我猜这是标准委员会的意图,但我不知道如何阅读标准来获得这个结果。
这个论点也适用于答案#1 中的plus_equal 函数。该实现不需要那么简单。此外,#1 提供了基于{} 的+= 语法,这是使用它的实际原因。这种担心——程序在技术上是错误的——是学术上的,因为我使用的所有编译器都没有这个结构的问题。
上面的第三段给了我们最后的选择。什么都不做。
template <typename T>
class A {
public:
A& operator +=(const T &rhs) {
mValue += rhs;
return *this;
}
private:
T mValue;
};
这意味着您不能 SFINAE 测试 += 不起作用,但只要您不调用 += 它“起作用”。例如,vector 的 operator< 就是这样工作的。这是一个“质量”较低的解决方案,标准库中的这种情况往往会随着时间的推移而得到修复。
但是,作为第一次通过,最后的选择通常是最好的。只有当您期望 SFINAE 要求时,上述箍筋才值得。
最终,C++1z 正在引入概念。我相信概念会让这个问题变得更容易,因为基于封闭类的类型参数从考虑中消除重载是std 中的一个长期问题。