You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否从整数字面量推导模板参数实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 23:42:32