如何通过模板在单个声明中重载const与非const运算符?
实现与目标类型行为一致的stalker模板类
要实现一个与Obj行为几乎一致的stalker<Obj>,核心是通过模板化运算符转发+完美转发避免手动重载所有运算符,同时利用decltype(auto)自动处理返回值(包括void),通过ref-qualifier区分const/非const、左值/右值版本。
1. 基础结构与构造函数
先定义持有目标对象的模板类,用完美转发构造参数初始化内部对象:
#include <utility> #include <type_traits> template <typename T> class stalker { private: T obj_; public: // 完美转发构造函数,适配所有T的构造方式 template <typename... Args> constexpr stalker(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>) : obj_(std::forward<Args>(args)...) {}
2. 处理成员运算符(如[]、())
针对成员运算符,通过ref-qualifier重载区分const/非const、左值/右值场景,用decltype(auto)自动推导返回值,无需专门处理void返回的情况:
示例:下标运算符[]
// 辅助函数:复用下标逻辑 template <typename Self, typename Index> static constexpr decltype(auto) subscript_impl(Self&& self, Index&& idx) noexcept( noexcept(std::forward<Self>(self).obj_[std::forward<Index>(idx)]) ) { return std::forward<Self>(self).obj_[std::forward<Index>(idx)]; } // 左值非const版本 template <typename Index> constexpr decltype(auto) operator[](Index&& idx) & { return subscript_impl(*this, std::forward<Index>(idx)); } // 左值const版本 template <typename Index> constexpr decltype(auto) operator[](Index&& idx) const& { return subscript_impl(*this, std::forward<Index>(idx)); } // 右值非const版本 template <typename Index> constexpr decltype(auto) operator[](Index&& idx) && { return subscript_impl(std::move(*this), std::forward<Index>(idx)); } // 右值const版本 template <typename Index> constexpr decltype(auto) operator[](Index&& idx) const&& { return subscript_impl(std::move(*this), std::forward<Index>(idx)); }
示例:函数调用运算符()
同理,处理函数调用运算符:
// 辅助函数:复用调用逻辑 template <typename Self, typename... Args> static constexpr decltype(auto) call_impl(Self&& self, Args&&... args) noexcept( noexcept(std::forward<Self>(self).obj_(std::forward<Args>(args)...)) ) { return std::forward<Self>(self).obj_(std::forward<Args>(args)...); } template <typename... Args> constexpr decltype(auto) operator()(Args&&... args) & { return call_impl(*this, std::forward<Args>(args)...); } template <typename... Args> constexpr decltype(auto) operator()(Args&&... args) const& { return call_impl(*this, std::forward<Args>(args)...); } template <typename... Args> constexpr decltype(auto) operator()(Args&&... args) && { return call_impl(std::move(*this), std::forward<Args>(args)...); } template <typename... Args> constexpr decltype(auto) operator()(Args&&... args) const&& { return call_impl(std::move(*this), std::forward<Args>(args)...); }
3. 处理一元运算符(如++、--、!)
针对一元运算符,同样通过ref-qualifier区分场景,根据目标类型的返回值决定返回stalker包装或原返回值:
// 前缀++ constexpr decltype(auto) operator++() & noexcept(noexcept(++obj_)) { if constexpr (std::is_lvalue_reference_v<decltype(++obj_)>) { ++obj_; return *this; // 保持链式调用,返回stalker引用 } else if constexpr (std::is_same_v<decltype(++obj_), void>) { ++obj_; // 返回void } else { // 返回包装后的stalker对象 return stalker<std::decay_t<decltype(++obj_)>>(++obj_); } } // const版本前缀++(如果Obj支持const前缀++) constexpr decltype(auto) operator++() const& noexcept(noexcept(++std::as_const(obj_))) { if constexpr (std::is_lvalue_reference_v<decltype(++std::as_const(obj_))>) { ++std::as_const(obj_); return *this; } else if constexpr (std::is_same_v<decltype(++std::as_const(obj_)), void>) { ++std::as_const(obj_); } else { return stalker<std::decay_t<decltype(++std::as_const(obj_))>>(++std::as_const(obj_)); } }
4. 处理二元运算符(如+、-、==)
二元运算符需要处理stalker在左右两边的情况,用友元模板实现,并根据返回值决定是否包装为stalker:
// 处理stalker在左边的+运算 template <typename U, typename V> friend constexpr decltype(auto) operator+(stalker<U> lhs, V&& rhs) noexcept( noexcept(lhs.obj_ + std::forward<V>(rhs)) ) { using result_t = decltype(lhs.obj_ + std::forward<V>(rhs)); if constexpr (std::is_same_v<result_t, void>) { lhs.obj_ + std::forward<V>(rhs); } else { return stalker<std::decay_t<result_t>>(lhs.obj_ + std::forward<V>(rhs)); } } // 处理stalker在右边的+运算 template <typename U, typename V> friend constexpr decltype(auto) operator+(U&& lhs, stalker<V> rhs) noexcept( noexcept(std::forward<U>(lhs) + rhs.obj_) ) { using result_t = decltype(std::forward<U>(lhs) + rhs.obj_); if constexpr (std::is_same_v<result_t, void>) { std::forward<U>(lhs) + rhs.obj_; } else { return stalker<std::decay_t<result_t>>(std::forward<U>(lhs) + rhs.obj_); } } };
5. 更简化的替代方案:继承(如果允许)
如果Obj不是final类,且允许直接继承,**public继承Obj**是最简洁的方案,自动继承所有运算符和成员函数:
template <typename T> class stalker : public T { public: using T::T; // 继承所有构造函数 // 如需拦截操作,可重写对应的成员函数 };
此方案局限性:无法拦截所有操作(用户可直接转换为Obj*),且如果Obj有私有构造函数则无法使用。
关键说明
- const/非const处理:通过ref-qualifier(
&/const&/&&/const&&)重载,确保const stalker<Obj>和stalker<const Obj>都能调用对应const版本的运算符。 - void返回处理:
decltype(auto)会自动推导返回void,无需额外的constexpr分支,完全匹配Obj的行为。 - 代码复用:通过辅助静态模板函数,将运算符的核心逻辑集中实现,减少重复代码。
内容的提问来源于stack exchange,提问作者Alexander S
相关产品推荐
相关产品推荐

