使用对临时变量的引用的表达模板可以重用吗?

问题描述

我正在努力将表情模板包裹住。在wikipedia article中,给出了一个示例,其中表达式模板VecSum存储对其两个操作数的 const引用Vec是包装std::vector<double>的表达式模板。我将首先提出我的问题,然后给出下面示例的完整摘要

我可以重用对常量使用const引用的表达式吗?如果没有,我将如何实现轻量,可重复使用的表达模板?

对于三个Vecab,表达式c的类型

a+b+c

如果我理解正确,则内部VecSum<VecSum<Vec,Vec>,Vec> 是临时的,外部VecSum存储对内部VecSum的const引用。我相信内部VecSum临时变量的生存期可以保证直到表达式VecSum得到求值。正确?这是否意味着在没有创建悬挂引用的危险的情况下不能重用该表达式?

a+b+c

如果是这样,如何修改此示例,以便

  • 表达式是可重用的
  • 表达式不存储其操作数的副本(至少在没有必要的情况下)

完整代码示例

出于完整性考虑,如果同时更新了维基百科的文章,请允许我在此处重复示例代码,并在auto expr = a + b + c; Vec v1 = expr; // ok Vec v2 = expr; // not ok! 中给出一个示例,我相信它会创建一个悬空的引用。

main

编辑:

我刚刚意识到这可能是this question的副本。但是,这两个问题的答案都大相径庭,而且都很有用。

解决方法

以上注释提供了一种非常有效的方法来检查悬挂参考的问题。请注意,如果您尝试从示例中的main函数打印值,则该程序仍将正常运行,因为绑定了悬挂引用的对象也会在main的堆栈空间上创建。我试图将分配给expr的代码移到函数内部,并且程序按预期崩溃(临时对象将在另一个堆栈框架中):

auto makeExpr1(Vec const& v0,Vec const& v1,Vec const& v2) {
    return v0 + v1 + v2;
}
// ... in main:
auto expr = makeExpr1(v0,v1,v2);

您在此处突出显示的问题出现在创建表达式时,这些表达式可以使用C ++等语言进行惰性计算。在范围表达式(C ++ 20范围)的上下文中,可能会发生某种类似的情况。 下面是我快速尝试修复该代码,并使其与通过运算符+添加的左值和右值一起使用的情况(我为其中的难看部分和可能的错误表示歉意)。仅在它们的操作数超出范围时,这将存储其操作数的副本,并且将导致旧代码中的悬挂引用。

关于可重用性:只要为每个操作定义一个类型并指定一个对应的运算符“?”函数(“?”是运算的符号),此方法应为您提供对此类向量进行任何二进制运算的起点。

#include <cassert>
#include <vector>
#include <utility>
#include <iostream>

/*
 * Passes lvalues and stores rvalues
 */
template <typename T> class Wrapper;

template <typename T> class Wrapper<T&> {
    private:
        T& ref;

    public:
        Wrapper(T& ref) : ref(ref) {}
        T& get() { return ref; }
        const T& get() const { return ref; }
};

template <typename T> class Wrapper<T&&> {
    private:
        T value;

    public:
        Wrapper(T&& ref) : value(std::move(ref)) {}
        T& get() { return value; }
        const T& get() const { return value; }
};


template <typename E>
class VecExpression {
  public:
    double operator[](size_t i) const 
    {
        // Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
        return static_cast<E const&>(*this)[i];
    }
    size_t size()               const { return static_cast<E const&>(*this).size(); }
};


/*
 * Forwards the reference and const qualifiers
 *  of the expression type to the expression itself
 */
template <typename E> constexpr E& forwardRef(VecExpression<E>& ve) {
    return static_cast<E&>(ve);
}

template <typename E> constexpr const E& forwardRef(const VecExpression<E>& ve) {
    return static_cast<const E&>(ve);
}

template <typename E> constexpr E&& forwardRef(VecExpression<E>&& ve) {
    return static_cast<E&&>(ve);
}


class Vec : public VecExpression<Vec> {
    std::vector<double> elems;

  public:
    double operator[](size_t i) const { return elems[i]; }
    double &operator[](size_t i)      { return elems[i]; }
    size_t size() const               { return elems.size(); }

    Vec(size_t n) : elems(n) {}

    // construct vector using initializer list 
    Vec(std::initializer_list<double> init) : elems(init) {}

    // A Vec can be constructed from any VecExpression,forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& expr) : elems(expr.size()) {
        std::cout << "Expr ctor\n"; // Very quick test
        for (size_t i = 0; i != expr.size(); ++i) {
            elems[i] = expr[i];
        }
    }

    // Move ctor added for checking
    Vec(Vec&& vec) : elems(std::move(vec.elems)) {
        std::cout << "Move ctor\n";  // Very quick test
    }
};


/*
 * Now VecSum is a sum between possibly const - qualified
 *  and referenced expression types
 */
template <typename E1,typename E2>
class VecSum : public VecExpression<VecSum<E1,E2>> {

    Wrapper<E1> _u;
    Wrapper<E2> _v;

public:

    VecSum(E1 u,E2 v) : _u(static_cast<E1>(u)),_v(static_cast<E2>(v)) {
        assert(_u.get().size() == _v.get().size());
    }

    double operator[](size_t i) const { return _u.get()[i] + _v.get()[i]; }
    size_t size()               const { return _v.get().size(); }
};

/*
 * Used to create a VecSum by capturing also the reference kind
 *  of the arguments (will be used by the Wrapper inside VecSum)
 */
template <typename E1,typename E2>
auto makeVecSum(E1&& e1,E2&& e2) {
    return VecSum<E1&&,E2&&>(std::forward<E1>(e1),std::forward<E2>(e2));
}


/*
 * Now the operator+ takes the vector expressions by universal references
 */
template <typename VE1,typename VE2>
auto operator+(VE1&& ve1,VE2&& ve2) {
   return makeVecSum(forwardRef(std::forward<VE1>(ve1)),forwardRef(std::forward<VE2>(ve2)));
}


// Now this will work
auto makeExpr1(Vec const& v0,Vec const& v2) {
    return v0 + v1 + v2;
}

// This will also work - the rvalue is stored in the
//  expression itself and both will have the same lifetime
auto makeExpr2(Vec const& v0,Vec const& v1) {
    return v0 + v1 + Vec({1.0,1.0,1.0});
}

int main() {

    Vec v0 = {23.4,12.5,144.56,90.56};
    Vec v1 = {67.12,34.8,90.34,89.30};
    Vec v2 = {34.90,111.9,45.12,90.5};

    auto expr = makeExpr1(v0,v2);
    Vec v1_ = expr;
    Vec v2_ = expr;
    auto expr_ = makeExpr2(v0,v1);

    for (size_t i = 0; i < v1_.size(); ++i)
        std::cout << v1_[i] << " ";
    std::cout << std::endl;

    for (size_t i = 0; i < v2_.size(); ++i)
        std::cout << v2_[i] << " ";
    std::cout << std::endl;

    for (size_t i = 0; i < expr.size(); ++i)
        std::cout << expr[i] << " ";
    std::cout << std::endl;

    for (size_t i = 0; i < expr_.size(); ++i)
        std::cout << expr_[i] << " ";
    std::cout << std::endl;
}