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

如何让C++模板表达式中的operator+正确工作?

表达式模板中operator+与原生类型兼容的问题解决

问题背景

正在为自定义Int类实现C++表达式模板,目标是让Int实例能在表达式模板树中与原生数值类型(如int)正常执行operator+操作,但目前无法实现正确的类型转换。

复现问题的最小代码

#include <type_traits>

template <typename E>
class IntExpr
{
    public:
        //typedef typename E::Type Type; //KO

        static constexpr bool is_leaf = false;

        template <typename T>
        T number() const
        {
            return static_cast<E const&>(*this).number();
        }

        template <typename T>
        IntExpr& operator+=(T const& other)
        {
            *this = *this + other;

            return *this;
        }
};

template <typename E1, typename E2>
class IntAdd : public IntExpr<IntAdd<E1, E2> >
{
    const E1 u_;
    const E2 v_;

    public:
        static constexpr bool is_leaf = false;

        IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v)
        {
        }
};

template <typename T>
class Int : public IntExpr<Int<T> >
{
    public:
        typedef T Type;

        static constexpr bool is_leaf = true;

        template <typename E>
        Int(IntExpr<E> const& expr)
        {
            number_ = expr.number();
        }

        explicit Int(int number) : number_{number}
        {

        }

        T number() const
        {
            return number_;
        }

        Int& operator+=(Int const& other)
        {
            number_ += other.number_;

            return *this;
        }

    private:
        T number_;
};

template <typename E1, typename E2>
inline IntAdd<E1, E2> operator+(IntExpr<E1> const& u, IntExpr<E2> const& v)
{
   return IntAdd<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}
template <typename T, typename S, typename std::enable_if_t<!std::is_base_of_v<S, T> >* = nullptr>
inline decltype(auto) operator+(IntExpr<T> const& lhs, S const& rhs)
{
    //return lhs + Int<typename IntExpr<T>::Type>(rhs); //KO
    return lhs + T(rhs);
}

template <typename T, typename S, typename std::enable_if_t<!std::is_base_of_v<S, T> >* = nullptr>
inline decltype(auto) operator+(S const& lhs, IntExpr<T> const& rhs)
{
    //return rhs + Int<typename IntExpr<T>::Type>(lhs); //KO
    return rhs + T(lhs);
}

int main()
{
    auto const a((Int<int>(1) + 2) + 3);
    auto const b((Int<long>(1) + 2) + 3);

    return 0;
}

报错信息

当尝试将Int与原生int相加时,编译器报错:

D:\programmation\cpp\TestTemplateExpression\main.cpp: In instantiation of 'decltype(auto) operator+(const IntExpr<E1>&, const S&) [with T = IntAdd<Int<int>, Int<int> >; S = int; std::enable_if_t<(! is_base_of_v<S, T>)>* <anonymous> = 0]':
D:\programmation\cpp\TestTemplateExpression\main.cpp:96:38:   required from here
D:\programmation\cpp\TestTemplateExpression\main.cpp:84:18: error: no matching function for call to 'IntAdd<Int<int>, Int<int> >::IntAdd(const int&)'
   84 |     return lhs + T(rhs);
      |                  ^~~~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:34:9: note: candidate: 'IntAdd<E1, E2>::IntAdd(const E1&, const E2&) [with E1 = Int<int>; E2 = Int<int>]'
   34 |         IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v)
      |         ^~~~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:34:9: note:   candidate expects 2 arguments, 1 provided
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note: candidate: 'constexpr IntAdd<Int<int>, Int<int> >::IntAdd(const IntAdd<Int<int>, Int<int> >&)'
   26 | class IntAdd : public IntExpr<IntAdd<E1, E2> >
      |       ^~~~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note:   no known conversion for argument 1 from 'const int' to 'const IntAdd<Int<int>, Int<int> >&'
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note: candidate: 'constexpr IntAdd<Int<int>, Int<int> >::IntAdd(IntAdd<Int<int>, Int<int> >&&)'
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note:   no known conversion for argument 1 from 'const int' to 'IntAdd<Int<int>, Int<int> >&&'

尝试通过IntExpr获取嵌套类型Type时,因类不完整导致报错:

:\programmation\cpp\TestTemplateExpression\main.cpp: In instantiation of 'class IntExpr<Int<int> >':
D:\programmation\cpp\TestTemplateExpression\main.cpp:41:7:   required from 'class Int<int>'
D:\programmation\cpp\TestTemplateExpression\main.cpp:96:29:   required from here
D:\programmation\cpp\TestTemplateExpression\main.cpp:7:34: error: invalid use of incomplete type 'class Int<int>'
    7 |         typedef typename E::Type Type; //KO
      |                                  ^~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:41:7: note: declaration of 'class Int<int>'
   41 | class Int : public IntExpr<Int<T> >
      |       ^~~

核心需求

需要实现一个类型萃取工具,能够从任意表达式模板节点(无论是叶子节点Int<T>还是复合节点如IntAdd<E1,E2>)中提取出底层的数值类型T,从而将原生数值转换为对应的Int<T>实例参与表达式运算。

解决思路:类型萃取器

通过递归的类型萃取模板,区分叶子节点和复合节点:

  • 对于叶子节点Int<T>,直接返回T
  • 对于复合节点(如IntAdd<E1,E2>),递归萃取其左操作数的底层类型(或根据需求选择右操作数,或做类型提升)

完整修复代码

#include <type_traits>
#include <iostream>

// 类型萃取器:提取表达式的底层数值类型
template<typename E>
struct underlying_type;

template<typename T>
struct underlying_type<Int<T>> {
    using type = T;
};

template<typename E1, typename E2>
struct underlying_type<IntAdd<E1, E2>> {
    // 递归提取左操作数的底层类型,也可根据需求做类型提升(如std::common_type_t)
    using type = typename underlying_type<E1>::type;
};

// 便利别名
template<typename E>
using underlying_type_t = typename underlying_type<E>::type;

template <typename E>
class IntExpr
{
    public:
        static constexpr bool is_leaf = false;

        template <typename T>
        T number() const
        {
            return static_cast<E const&>(*this).number();
        }

        template <typename T>
        IntExpr& operator+=(T const& other)
        {
            *this = *this + other;
            return *this;
        }
};

template <typename E1, typename E2>
class IntAdd : public IntExpr<IntAdd<E1, E2> >
{
    const E1 u_;
    const E2 v_;

    public:
        static constexpr bool is_leaf = false;

        IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v) {}

        // 实现number()方法,计算表达式结果
        auto number() const {
            return u_.number() + v_.number();
        }
};

template <typename T>
class Int : public IntExpr<Int<T> >
{
    public:
        typedef T Type;
        static constexpr bool is_leaf = true;

        template <typename E>
        Int(IntExpr<E> const& expr) : number_(expr.number()) {}

        // 支持从原生数值构造
        template<typename S, std::enable_if_t<std::is_arithmetic_v<S>>* = nullptr>
        explicit Int(S number) : number_(static_cast<T>(number)) {}

        T number() const { return number_; }

        Int& operator+=(Int const& other) {
            number_ += other.number_;
            return *this;
        }

    private:
        T number_;
};

template <typename E1, typename E2>
inline IntAdd<E1, E2> operator+(IntExpr<E1> const& u, IntExpr<E2> const& v)
{
   return IntAdd<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}

// 原生数值与表达式相加:转换为对应底层类型的Int实例
template <typename E, typename S, std::enable_if_t<std::is_arithmetic_v<S>>* = nullptr>
inline decltype(auto) operator+(IntExpr<E> const& lhs, S const& rhs)
{
    using IntType = Int<underlying_type_t<E>>;
    return lhs + IntType(rhs);
}

template <typename E, typename S, std::enable_if_t<std::is_arithmetic_v<S>>* = nullptr>
inline decltype(auto) operator+(S const& lhs, IntExpr<E> const& rhs)
{
    using IntType = Int<underlying_type_t<E>>;
    return IntType(lhs) + rhs;
}

int main()
{
    auto const a((Int<int>(1) + 2) + 3);
    auto const b((Int<long>(1) + 2) + 3);

    std::cout << "a's value: " << a.number() << std::endl; // 输出6
    std::cout << "b's value: " << b.number() << std::endl; // 输出6

    return 0;
}

关键改进点

  1. 类型萃取器underlying_type:递归解析表达式节点的底层数值类型,解决了IntExpr中无法直接访问E::Type的问题(避免了类不完整的错误)。
  2. 优化Int的构造函数:添加了通用的原生数值构造函数,支持所有算术类型转换。
  3. 修正operator+重载:利用萃取器获取的类型创建对应的Int实例,确保原生数值能正确转换为表达式模板兼容的类型。
  4. 补充IntAdd的number()方法:实现表达式的计算逻辑,让表达式模板能正确求值。

临时解决方案(固定类型)

如果暂时不需要动态提取类型,可采用固定类型的转换,但会丢失类型信息:

#include <iostream>
#include <type_traits>

template <typename E>
class IntExpr
{
    public:
        static constexpr bool is_leaf = false;

        constexpr decltype(auto) number() const
        {
            return static_cast<E const&>(*this).number();
        }
};

template <typename E1, typename E2>
class IntAdd : public IntExpr<IntAdd<E1, E2> >
{
    public:
        constexpr IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v) {}

        constexpr decltype(auto) number() const
        {
            return u_.number() + v_.number();
        }

    private:
        E1 const u_;
        E2 const v_;
};

template <typename T>
class Int : public IntExpr<Int<T> >
{
    public:
        static constexpr bool is_leaf = true;

        template <typename E>
        constexpr Int(IntExpr<E> const& expr) : number_(expr.number()) {}

        template <typename S, std::enable_if_t<std::is_standard_layout_v<S> && std::is_trivial_v<S> >* = nullptr>
        constexpr Int(S const& number) : number_(static_cast<T>(number)) {}

        constexpr T const& number() const
        {
            return number_;
        }

    private:
        T number_{0};
};

template <typename E1, typename E2>
constexpr inline IntAdd<E1, E2> operator+(IntExpr<E1> const& u, IntExpr<E2> const& v)
{
   return IntAdd<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}

template <typename E, typename T, typename std::enable_if_t<std::is_standard_layout_v<T> && std::is_trivial_v<T> >* = nullptr>
constexpr inline decltype(auto) operator+(IntExpr<E> const& u, T const& v)
{
    return IntAdd<E, Int<char> >(*static_cast<const E *>(&u), Int<char>(v));
}

template <typename E, typename T, typename std::enable_if_t<std::is_standard_layout_v<T> && std::is_trivial_v<T> >* = nullptr>
constexpr inline decltype(auto) operator+(T const& u, IntExpr<E> const& v)
{
    return IntAdd<Int<char>, E>(Int<char>(u), *static_cast<const E *>(&v));
}

int main()
{
    auto const a(Int<long>(1));
    auto const b(Int<long>(1) + 2);
    auto const c((Int<long>(1) + 2) + 3);

    std::cout << "a type " << typeid(a).name() << std::endl;
    std::cout << "a number " << a.number() << std::endl; //expected 1
    std::cout << "b type " << typeid(b).name() << std::endl;
    std::cout << "b number " << b.number() << std::endl; //expected 3
    std::cout << "c type " << typeid(c).name() << std::endl;
    std::cout << "c number " << c.number() << std::endl; //expected 6

    return 0;
}

内容的提问来源于stack exchange,提问作者P'tit Ju

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:30:52