能否从整数字面量推导模板参数实现MyRange类隐式转换?
问题
我有一个将整数包装为仅编译器(及开发者)可知范围的类MyRange,其范围在运行时不可知。该类实现的运算符会改变范围,并返回带有修改后范围的新类型值(这一点至关重要)。
以下是该类的示例代码,实现了上述的+运算符(支持C++20编译):
#include <iostream> using namespace std; template< int LOWER_, int UPPER_ > class MyRange final { public: constexpr static int LOWER = LOWER_; constexpr static int UPPER = UPPER_; template< int, int > friend class MyRange; constexpr MyRange(MyRange const &) noexcept = default; constexpr MyRange(MyRange&&) noexcept = default; constexpr ~MyRange() {} template< int VALUE > requires ( VALUE >= LOWER && VALUE <= UPPER ) static constexpr MyRange wrap = MyRange( VALUE ); template< class _RHS, int _RHS_LOWER = _RHS::LOWER, int _RHS_UPPER = _RHS::UPPER, int _RES_LOWER = LOWER + _RHS_LOWER, int _RES_UPPER = UPPER + _RHS_UPPER, typename _RESULT_T = MyRange<_RES_LOWER, _RES_UPPER> > friend _RESULT_T operator+(MyRange const lhs, _RHS const &rhs) noexcept { int result = lhs.value + rhs.unwrap(); return construct<_RESULT_T>( result ); } int unwrap() const noexcept { return value; } private: MyRange() = delete; MyRange& operator=(MyRange const &) = delete; MyRange& operator=(MyRange&&) = delete; // 此构造函数不可暴露,因为值必须在编译时检查范围; // wrap<VALUE>是公开的“构造函数” explicit constexpr MyRange(int value) noexcept : value(value) {} // 辅助函数:构造MyRange的另一个特化版本 template< class TO > static constexpr TO construct(int value) noexcept { return TO(value); } int const value; };
使用示例:
int main() { auto value = MyRange<5,20>::wrap<8>; auto another = MyRange<6,10>::wrap<6>; auto result = value + another; // 输出:14; limits: 11, 30 cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl; }
现在我遇到一个问题:希望能够将整数字面量与该范围类的变量相加。若通过显式或隐式转换实现,会导致范围不必要地扩大:
using Range = MyRange<5,20>; auto value = Range::wrap<8>; auto result = value + Range::wrap<6>; // 输出:14; limits: 10, 40 cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl;
当然,我可以显式包装整数字面量来获得理想结果:
auto value = MyRange<5,20>::wrap<8>; auto result = value + MyRange<6,6>::wrap<6>; // 输出:14; limits: 11, 26 cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl;
但这种方式对用户来说冗余度太高,我更希望直接编写auto result = value + 6;,让整数字面量6在传入运算符前隐式转换为MyRange<6,6>::wrap<6>。
请问能否在编译时实现这一功能?
我曾尝试使用带有值参数的consteval函数来创建所需的MyRange类型,但遗憾的是,即使consteval函数保证在编译时执行,其参数也不是constexpr。我需要的是某种能从编译时已知的公式中提取整数字面量,进而创建以该字面量为模板参数的目标类型的方法。
解决方案
可以通过为operator+添加针对编译时常量整数的模板重载实现需求,利用C++20特性捕获字面量值,在编译时生成对应范围的MyRange<V,V>类型。
1. 核心修改:添加常量整数的运算符重载
在MyRange类内部新增模板重载,直接接受编译时已知的整数值作为模板参数,生成精确范围的结果类型:
// 针对编译时常量整数的重载 template<int V> friend constexpr MyRange<LOWER + V, UPPER + V> operator+(MyRange const lhs, std::integral_constant<int, V> const&) noexcept { return construct<MyRange<LOWER + V, UPPER + V>>(lhs.value + V); }
2. 全局辅助重载:自动转换字面量
添加全局operator+重载,让普通整数字面量自动转换为std::integral_constant,仅在编译时求值场景下生效:
#include <type_traits> // 全局重载:将字面量int转换为integral_constant(仅编译时生效) template<int L, int U> constexpr auto operator+(MyRange<L, U> const lhs, int rhs) noexcept requires std::is_constant_evaluated() { return lhs + std::integral_constant<int, rhs>{}; } // 反向重载:支持6 + value的写法 template<int L, int U> constexpr auto operator+(int lhs, MyRange<L, U> const rhs) noexcept requires std::is_constant_evaluated() { return rhs + std::integral_constant<int, lhs>{}; }
修改后的完整代码
#include <iostream> #include <type_traits> using namespace std; template< int LOWER_, int UPPER_ > class MyRange final { public: constexpr static int LOWER = LOWER_; constexpr static int UPPER = UPPER_; template< int, int > friend class MyRange; constexpr MyRange(MyRange const &) noexcept = default; constexpr MyRange(MyRange&&) noexcept = default; constexpr ~MyRange() {} template< int VALUE > requires ( VALUE >= LOWER && VALUE <= UPPER ) static constexpr MyRange wrap = MyRange( VALUE ); // 原有的MyRange间的加法重载 template< class _RHS, int _RHS_LOWER = _RHS::LOWER, int _RHS_UPPER = _RHS::UPPER, int _RES_LOWER = LOWER + _RHS_LOWER, int _RES_UPPER = UPPER + _RHS_UPPER, typename _RESULT_T = MyRange<_RES_LOWER, _RES_UPPER> > friend _RESULT_T operator+(MyRange const lhs, _RHS const &rhs) noexcept { int result = lhs.value + rhs.unwrap(); return construct<_RESULT_T>( result ); } // 针对编译时常量整数的重载 template<int V> friend constexpr MyRange<LOWER + V, UPPER + V> operator+(MyRange const lhs, std::integral_constant<int, V> const&) noexcept { return construct<MyRange<LOWER + V, UPPER + V>>(lhs.value + V); } int unwrap() const noexcept { return value; } private: MyRange() = delete; MyRange& operator=(MyRange const &) = delete; MyRange& operator=(MyRange&&) = delete; explicit constexpr MyRange(int value) noexcept : value(value) {} template< class TO > static constexpr TO construct(int value) noexcept { return TO(value); } int const value; }; // 全局重载:将字面量int转换为integral_constant(仅编译时生效) template<int L, int U> constexpr auto operator+(MyRange<L, U> const lhs, int rhs) noexcept requires std::is_constant_evaluated() { return lhs + std::integral_constant<int, rhs>{}; } // 反向重载:支持6 + value的写法 template<int L, int U> constexpr auto operator+(int lhs, MyRange<L, U> const rhs) noexcept requires std::is_constant_evaluated() { return rhs + std::integral_constant<int, lhs>{}; }
验证示例
int main() { auto value = MyRange<5,20>::wrap<8>; auto result = value + 6; // 输出:14; limits: 11, 26 cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl; // 支持反向加法 auto result2 = 6 + value; cout << result2.unwrap() << "; limits: 11, 26" << endl; // 运行时变量会触发编译错误(符合预期,因为无法捕获运行时值作为模板参数) // int runtime_val = 6; // auto result3 = value + runtime_val; // 编译失败,符合设计意图 }
原理说明
- 当使用整数字面量时,
std::is_constant_evaluated()返回true,全局重载生效,编译器将字面量推导为模板参数,生成std::integral_constant<int,V>实例,进而调用对应operator+重载,返回精确范围的MyRange对象。 - 对于运行时
int变量,std::is_constant_evaluated()返回false,重载被禁用,编译报错,确保MyRange的范围始终由编译时确定,符合设计初衷。
内容的提问来源于stack exchange,提问作者wolfjazz
相关产品推荐
相关产品推荐

