仅当模板参数有重载运算符时
Only overload operator if template argument does
给定一个模板类 A 具有单个模板参数 T,是否可以只重载 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 有运算符 +=,则 A 将(在编译时)相应地重载 +=。是的,我可以继续实现 A 中的每个运算符,但是当 T 没有某个运算符时,编译器会出错。起初,我认为模板专业化可能是答案,但这需要每种类型都有专业化。虽然这可以工作并且需要大量键入,但它不会,因为 A 需要处理任何类型(而不仅仅是专用类型)。
使用表达式 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;
}
};
现场演示
我将给出 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;
};
这为您提供了一个+=
重载,当且仅当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
中长期存在的问题。
你实际上不需要做任何事情。模板类的各个成员函数在使用之前不会实例化。你说:
但是当编译器没有特定的运算符时,编译器会出错
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')
}
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 重载运算符new[]的行为取决于析构函数
- 为什么将值返回函数传递给重载=运算符对运算符函数有效,而对其他运算符无效
- 在 myVector 类中重载运算符 + 时出错
- 为什么常量词在重载运算符中不与 ostream 对象一起使用<<?
- 如何在 cpp 中重载运算符 +=?
- C++ 如何重载 [] 运算符并进行函数调用
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 为什么我可以在不重载 "=" 运算符的情况下将一个对象分配给另一个对象?
- 重载运算符有地址吗?
- 如何迭代重载运算符 [] 的类?
- 重载运算符与添加问题
- 模板基类中的重载运算符
- 如何调用用于重载运算符"<<"的 friend 函数?
- 在 C++17 中的命名空间和子命名空间中重载运算符是不明确的
- 重载运算符<<采用谷歌 C++ 风格
- C++ 如何正确重载 + 运算符
- cout (<<) 重载运算符不打印减去的矩阵
- 如何在 c++ 中重载运算符 + 以便能够 whrite c_str = "smth" + c_str;
- 重载运算符*以获取对另一个类的实例的引用