C++ 如何在C++;

C++ 如何在C++;,c++,c++11,templates,C++,C++11,Templates,我尝试做的一些背景知识: 我正在尝试实现一个做量子力学的库。由于量子力学基本上只是线性代数,我正在使用下面的犰狳线性代数库。Armadillo使用惰性求值对矩阵进行了一些巧妙的处理,这从实际情况中提供了一个非常好的抽象,看起来很接近matlab代码 我想做一些类似的事情,但我也希望能够使用auto,这在犰狳(或eigen)身上是不可能的 我已经环顾了一下,这个答案包含了我认为是实现这一点的典型方式: 这种方法的问题在于,当您编写 auto C = A+B; 您将得到一个C,它是一个matrix

我尝试做的一些背景知识: 我正在尝试实现一个做量子力学的库。由于量子力学基本上只是线性代数,我正在使用下面的犰狳线性代数库。Armadillo使用惰性求值对矩阵进行了一些巧妙的处理,这从实际情况中提供了一个非常好的抽象,看起来很接近matlab代码

我想做一些类似的事情,但我也希望能够使用
auto
,这在犰狳(或eigen)身上是不可能的

我已经环顾了一下,这个答案包含了我认为是实现这一点的典型方式:

这种方法的问题在于,当您编写

auto C = A+B;
您将得到一个
C
,它是一个
matrix\u add
,而不是
matrix
。即使
matrix\u add
的行为类似于
matrix
,但
matrix\u add
包含对
A
B
的引用这一事实也会让人难以随身携带。例如

auto A = matrix(2,2,{0,1,0,1});
auto B = matrix(2,2,{1,0,1,0});
auto C = A+B;
C.printmatrix(); // 1,1 ; 1,1
但是

这是违反直觉的。由于数学直觉行为是我想要实现的,这是一个问题

更糟糕的是当我这样做的时候

auto sumMatrices(const matrix& A, const matrix& B)
{
    return A+B;
}
它返回一个
矩阵\u add
,并引用本地内存

我真的希望能够有好的、重载的行为,但也能够使用
auto
。我的想法是制作一个可以保存引用或实例的包装器:

template<class T>
class maybe_reference
{
public:
    maybe_reference(const T& t):
        ptr_(std::make_unique<T>(t)),
        t_(*ptr_)
    {}
    maybe_reference(std::reference_wrapper<const T> t): 
        t_(t.get())
    {}

    const T& get(){return t_;}
private:
    unique_ptr<T> ptr_;
    const T& t_;
}
我省略了所有使
matrix\u add
表现得像
矩阵
的部分。这个想法是让对象引用外部对象A和B,只要它是用A+B构造的,但是当它被移动构造时,它将拥有副本

我的问题基本上是:这有效吗

我一直在想,在某些或所有情况下,移动构造函数可能会被忽略,这可能是毁灭性的

此外,是否有其他方法可以实现同样的目标?我一直在寻找,但似乎对于线性代数,至少它要么是懒惰的,要么是自动的

编辑:多亏有人提醒我“表达式模板”这个词,我的谷歌搜索结果好多了。我发现了这篇reddit帖子:

以及参考文献,它们允许自动指定“类型转换”。这将是真正使所有这些都能工作的功能

您可以编写一个模板函数
evaluate
,默认情况下它是一个NOP,然后根据需要重载

#include <utility>
#include <type_traits>

struct matrix {};
struct matrix_add {
  matrix operator()() const;
};

matrix_add operator + (matrix const& a, matrix const& b);


template<class T> decltype(auto) evaluate(T&& val) { return std::forward<T>(val); }
matrix evaluate(matrix_add const& lazy) { return lazy(); }
matrix evaluate(matrix_add & lazy) { return lazy(); }
matrix evaluate(matrix_add && lazy) { return lazy(); }



int main()
{
  auto a = matrix();
  auto b = matrix();

  auto c = evaluate(a + b);
  auto d = evaluate(1 + 2);

  static_assert(std::is_same<decltype(c), matrix>::value, "");
  static_assert(std::is_same<decltype(d), int>::value, "");

}
#包括
#包括
结构矩阵{};
结构矩阵{
矩阵算子()()常数;
};
矩阵加运算符+(矩阵常数a、矩阵常数b);
模板decltype(自动)求值(T&&val){return std::forward(val);}
矩阵求值(矩阵_add const&lazy){return lazy();}
矩阵求值(矩阵_add&lazy){return lazy();}
矩阵求值(矩阵_add&&lazy){return lazy();}
int main()
{
自动a=矩阵();
自动b=矩阵();
自动c=评估(a+b);
自动d=评估(1+2);
静态断言(std::is_same::value,“”);
静态断言(std::is_same::value,“”);
}

使用c++17类模板参数推断,您可以编写

struct matrix_expr_foo {};
struct matrix_expr_bar {};

template< typename L, typename R >
struct matrix_add {
    // ...
};

matrix_add<matrix_expr_foo,matrix_expr_bar> operator + (matrix_expr_foo const& a, matrix_expr_bar const& b);

template< typename T >
struct expr {
    expr( T const& expr ){
        // evaluate expr ( to be stored in an appropriate member )
    }
    // ...
};

int main()
{
  auto a = matrix_expr_foo();
  auto b = matrix_expr_bar();
  expr c = a + b;

  /* different naming ?
  auto_ c = a + b;
  ...
  */
}
struct matrix_expr_foo{};
结构矩阵_expr_bar{};
模板
结构矩阵{
// ...
};
矩阵加法运算符+(矩阵表达式foo const&a,矩阵表达式bar const&b);
模板
结构表达式{
expr(T const&expr){
//evaluate expr(存储在适当的成员中)
}
// ...
};
int main()
{
自动a=矩阵_expr_foo();
自动b=矩阵表达式条();
expr c=a+b;
/*不同的命名?
自动_uuC=a+b;
...
*/
}

其中
expr
将充当表达式模板的
auto

我将定义一个新操作符:
eager\u eval
,如下所示:

namespace lazy {
  template<class T>
  void eager_eval(T const volatile&)=delete;

  template<class T>
  struct expression {
    template<class D,
      std::enable_if_t<std::is_base_of<expression, std::decay_t<D>>{}, int> =0
    >
    friend T eager_eval( D&& d ) { return std::forward<D>(d); }
  };
}
现在,任何人都可以做到:

auto e = eager_eval( a+b );
ADL找到了正确的类型来计算惰性表达式

您可以选择实现一个返回其参数的默认
eager\u eval

  template<class T, class...Ts>
  T eager_eval(T&& t, Ts&&...)  { return std::forward<T>(t); }
允许您不知道传递给
eager\u eval
的类型;如果它是一种通过
eager\u eval
重载意识到懒惰的类型,它会转换,如果不是,则不会转换


上面的lazy::eanger\u eval中的包是为了确保它作为重载具有最低的优先级。

我认为,您的基本问题是,lazy求值不能与不断变化的状态很好地结合。我认为有两种可能的途径:

  • 使你的矩阵不可变。如果你“修改”了一个矩阵,你实际上创建了一个包含更改的副本,原始的保持不变。这在语义上效果很好(任何数学都像您期望的那样工作),但是如果您逐个设置矩阵值,则可能会产生无法忍受的运行时开销

    这允许您的
    matrix\u add
    实现在对其求值时用
    matrix
    对象自动替换自身,确保每次求值最多只执行一次

  • 将函数显式化。不要创建
    matrix\u添加
    像矩阵一样工作的对象,而是创建
    matrix\u函数
    对象,对一些输入矩阵进行操作以产生一些结果。这允许您在您认为合适的地方显式执行评估,并重用您定义的函数。然而,这种方法将导致大量额外的代码复杂性


  • 我不认为通过引入强制评估的隐含点来解决这个问题是一个好主意:你会失去大部分可以通过懒惰评估实现的东西,那么为什么首先要麻烦呢?只有我的两分钱。

    是的,表达式模板和
    auto
    不能很好地混合。希望将来会增加一个机械师来满足您的需求。您可以考虑在al中使用
    struct matrix_add:
      lazy::expression<matrix>
    {
      matrix_add(matrix const& a, matrix const& b) : a(a), b(b) { }
    
      operator matrix() && { // rvalue ref qualified as it should be.
        matrix result;
        // Do the addition.
        return result;
      }
    private:
      matrix const& a, b;
    };
    
    auto e = eager_eval( a+b );
    
      template<class T, class...Ts>
      T eager_eval(T&& t, Ts&&...)  { return std::forward<T>(t); }
    
    using lazy::eager_eval;
    auto x = eager_eval( 1+2 );