草庐IT

C++ 表达式模板生命周期

coder 2024-02-05 原文

我在 https://en.wikipedia.org/wiki/Expression_templates 查看表达式模板的示例.然后我尝试制作一个简单的符号表达式树,即添加常量和变量,如 a + b + 10。所以我从

#include <iostream>


template<typename E>
class Expression {
public:
    std::ostream& print(std::ostream& os) const
    {
        return expression().print(os);
    }
    E const& expression() const { return static_cast<E const&>(*this); }
};

class Var : public Expression<Var> {
public:
    Var(const char name)
            : name_(name)
    {}

    std::ostream& print(std::ostream& os) const
    {
        return os << name_;
    }
private:
    const char name_;
};

class Constant : public Expression<Constant> {
public:
    Constant(const double value)
            : value_(value)
    {}

    std::ostream& print(std::ostream& os) const
    {
        return os << value_;
    }

private:
    const double value_;
};

template<typename E1, typename E2>
class ExpressionSum : public Expression<ExpressionSum<E1,E2>> {
    E1 const& u_;
    E2 const& v_;
public:
    ExpressionSum(E1 const& u, E2 const& v) : u_(u), v_(v)
    {
    }

    std::ostream& print(std::ostream& os) const
    {
        os << "(";
        u_.print(os);
        os << " + ";
        v_.print(os);
        os << ")";
        return os;
    }
};
template <typename E1, typename E2>
ExpressionSum<E1,E2> operator+(Expression<E1> const& u, Expression<E2> const& v) {
    return ExpressionSum<E1, E2>(u.expression(), v.expression());
}

int main() {
    Var a('a');
    Var b('b');
    Constant c(1.0);

    auto expr = a + b + c;
    expr.print(std::cout);
    std::cout << std::endl;

    auto expr2 = expr + Constant{2.0};
    expr2.print(std::cout);
    std::cout << std::endl;
}

表达式expr很好,但我不能重复使用 expr构建另一个表达式,如 expr2自临时ExpressionSuma+b已经被摧毁了。有没有办法在表达式中存储这些临时变量?

最佳答案

避免 CRTP:使用 Argument-Dependent Lookup 来简化库

我们想让事情尽可能简单。 Curiously Recurring Template Pattern(及其亲戚)是功能强大的工具,但它们会增加编译时间,并且在您想扩展您正在做的事情时会很麻烦。

利用 argument dependent lookup ,我们可以在没有基类的情况下实现运算符重载。这大大简化了库的设计。我将在下面给出的示例中对此进行更多解释

避免生命周期问题:按值存储子表达式,除非显式使用 std::ref

我们想让这个库保持简单。表达式可以是常量、一元运算和输入,也可以是二元运算和输入。没有任何类不变量——输入可以取任何值,操作本身是根据它的类型存储的,所以它只能有 1 个值。

这意味着我们可以将表达式表示为聚合类型,使它们易于构造、易于复制、易于破坏,并减少编译时间和生成的二进制文件的大小。

namespace expr // We need to put them in a namespace so we can use ADL
{
    template<class Value>
    class Constant
    {
       public:
        Value value;
    };

    template<class Op, class Input>
    class UnaryOp
    {
       public:
        Op op;
        Input input; 
    };
    template<class Op, class Left, class Right>
    class BinaryOp
    {
       public:
        Op op;
        Left lhs;
        Right rhs; 
    };
}

简化运算符重载:使用命名空间作用域

如果我们在命名空间中编写运算符重载,只有在处理来自该命名空间的类型时才会考虑它们。这意味着我们可以避免使用基类,并且可以使用不受约束的模板。

namespace expr 
{
    template<class A>
    auto operator-(A const& a)
    {
        return UnaryOp<Negate, A>{{}, a}; 
    }
    template<class A, class B>
    auto operator+(A const& a, B const& b) 
    {
        return BinaryOp<Plus, A, B>{{}, a, b}; 
    }
    template<class A, class B>
    auto operator-(A const& a, B const& b) 
    {
        return BinaryOp<Minus, A, B>{{}, a, b}; 
    }
    template<class A, class B>
    auto operator*(A const& a, B const& b) {
        return BinaryOp<Times, A, B>{{}, a, b}; 
    }
}

简化评估:操作类型知道如何评估他们的输入

这实现起来非常简单 - 基本上,任何操作都是一个知道如何评估输入的仿函数类型。在 C++20 中,这可以通过 lambda 来实现,但出于我们的目的,我们将重载 operator()

namespace expr {
    class Negate {
        template<class A>
        constexpr auto operator()(A&& a) const 
            noexcept(noexcept(-a))
            -> decltype(-a)
        {
            return -a; 
        }
    };
    class Plus {
    public:
        template<class A, class B>
        constexpr auto operator()(A&& a, B&& b) const
            noexcept(noexcept(a + b))
            -> decltype(a + b) 
        {
            return a + b; 
        }
    };
    class Minus {
    public:
        template<class A, class B>
        constexpr auto operator()(A&& a, B&& b) const
            noexcept(noexcept(a - b))
            -> decltype(a - b) 
        {
            return a - b; 
        }
    };
    class Times {
    public:
        template<class A, class B>
        constexpr auto operator()(A&& a, B&& b) const
            noexcept(noexcept(a * b))
            -> decltype(a * b) 
        {
            return a * b; 
        }
    };
}

利用命名空间范围的模式匹配evaluate

在命名空间范围内编写 evaluate 函数时,我们可以利用模式匹配和递归,而不是将其作为成员函数。

namespace expr
{
    // This one is applied to things that aren't constants or expressions
    template<class Thing>
    auto evaluate(Thing const& t) -> Thing const& {
        return t; 
    }
    template<class Value>
    auto evaluate(Constant<Value> const& value) {
        return evaluate(value.value);
    }
    template<class Op, class Input>
    auto evaluate(UnaryOp<Op, Input> const& expr) {
        return expr.op(evaluate(expr.value)); 
    }
    template<class Op, class LHS, class RHS>
    auto evaluate(BinaryOp<Op, LHS, RHS> const& expr) {
        return expr.op(evaluate(expr.lhs), evaluate(expr.rhs)); 
    }
}

关于C++ 表达式模板生命周期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56261171/

有关C++ 表达式模板生命周期的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  3. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  4. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  5. ruby-on-rails - Mandrill API 模板 - 2

    我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h

  6. ruby - 正则表达式将非英文字母匹配为非单词字符 - 2

    @raw_array[i]=~/[\W]/非常简单的正则表达式。当我用一些非拉丁字母(具体来说是俄语)尝试时,条件是错误的。我能用它做什么? 最佳答案 @raw_array[i]=~/[\p{L}]/使用西里尔字符进行测试。引用:http://www.regular-expressions.info/unicode.html#prop 关于ruby-正则表达式将非英文字母匹配为非单词字符,我们在StackOverflow上找到一个类似的问题: https://

  7. ruby - Chef Ruby 遍历 .erb 模板文件中的属性 - 2

    所以这可能有点令人困惑,但请耐心等待。简而言之,我想遍历具有特定键值的所有属性,然后如果值不为空,则将它们插入到模板中。这是我的代码:属性:#===DefaultfileConfigurations#default['elasticsearch']['default']['ES_USER']=''default['elasticsearch']['default']['ES_GROUP']=''default['elasticsearch']['default']['ES_HEAP_SIZE']=''default['elasticsearch']['default']['MAX_OP

  8. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  9. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  10. ruby - 有没有办法从 ruby​​ case 语句中访问表达式? - 2

    我想从then子句中访问c​​ase语句表达式,即food="cheese"casefoodwhen"dip"then"carrotsticks"when"cheese"then"#{expr}crackers"else"mayo"end在这种情况下,expr是食物的当前值(value)。在这种情况下,我知道,我可以简单地访问变量food,但是在某些情况下,该值可能无法再访问(array.shift等)。除了将expr移出到局部变量然后访问它之外,是否有直接访问caseexpr值的方法?罗亚附注我知道这个具体示例很简单,只是一个示例场景。 最佳答案

随机推荐