重写比较运算符和表达式模板

问题描述

我有一个强烈影响的有限体积库,该库使解决连续力学问题的解决方案与用C ++编写类似 将在纸上。例如,要解决不可压缩的层流的Navier-Stokes方程,我只需编写:

solve(div(U * U) - nu * lap(U) == -grad(p));

当然,此表达式包含表达式模板,以便单次计算系统系数,并以无矩阵方式求解系统。

那些表达式模板基于CRTP,因此所有必需的运算符都在基类中定义。特别是,等于运算符的定义如下:

template <typename T>
class BaseExpr {};

template <std::size_t dim,std::size_t rank,typename... TRest,template <std::size_t,std::size_t,typename...> typename T>
class BaseExpr<T<dim,rank,TRest...>>
{
// ...

public:
  template <typename... URest,typename...> typename U>
  SystemExpr<dim,T<dim,TRest...>,U<dim,URest...>>
  operator ==(BaseExpr<U<dim,URest...>> const &rhs) const
    { return {*this,rhs}; }

  SystemExpr<dim,Tensor<dim,rank>>
  operator ==(Tensor<dim,rank> const &rhs) const
    { return {*this,rhs}; }
};

其中SystemExpr<dim,URest...>>具有延迟评估系数和计算解的功能

与OpenFOAM相反,在OpenFOAM中,您必须限定fvm::lapfvc::grad等,以表明该术语将被明确评估或 隐式地,我采用了我一直在纸上使用的约定:左侧的所有内容都隐式评估,而右侧的所有内容都隐式评估 被明确评估。因此,BaseExpr::operator ==不是可交换的。随着方程变得更长,这变得越来越有用。例如,可压缩流的ε传输方程为:

solve(div(φ * ε) - div(µt * grad(ε)) / σε + ρ * Sp((C2 * ω + 2.0 / 3.0 * C1 * div(U)) * ε) == C1 * ω * G);

我担心在C ++ 20下此设计可能会由于operator ==的新“重写候选”而中断。 G ++-10会在没有任何警告的情况下编译库 与-std=c++20 -Wall -Wextra -pedantic一起使用,但我想再次检查:以上代码在C ++ 20下格式是否正确?

我知道有些人可能认为上面的设计很糟糕,但是我喜欢它,我宁愿停留在-std=c++17模式而不是使用 另一个表示相等的运算符(例如operator >>或其他)(是的,这是相等的一种形式)。


使用GCC-10.2,我得到了想要的行为。考虑非稳态传热方程:

solve(d(T) / dt - α * lap(T) == 0); // OK: implicit scheme
  
solve(d(T) / dt == α * lap(T)); // OK: explicit scheme

solve(d(T) / dt - 0.5 * α * lap(T) == 0.5 * α * lap(T)); // OK: Crank-Nicholson scheme
  
solve(0 == d(T) / dt - α * lap(T)); // OK: doesn't compile

最后一个示例不编译是完全可以的,因为它没有意义。我得到的错误是:

prova.cpp:51:11: error: return type of ‘VF::TExprSistema<d,r1,T<d,TRest ...>,VF::TTensor<d,r> > VF::TExprBase<T<d,TRest ...> >::operator==(const VF::TTensor<d,r>&) const [with long unsigned int d = 3; long unsigned int r1 = 0; TRest = {VF::TExprBinaria<3,VF::TExprd<3,0>,double,std::divides<void> >,VF::TExprBinaria<3,VF::TExprLap<3,void>,std::multiplies<void> >,std::minus<void>}; T = VF::TExprBinaria]’ is not ‘bool’
   51 |   solve(0 == d(T) / dt - α * lap(T));
      |         ~~^~~~~~~~~~~~~~~~~~~~~~~~~
prova.cpp:51:11: note: used as rewritten candidate for comparison of ‘int’ and ‘VF::TExprBinaria<3,std::minus<void> >’

因此,即使在C ++ 20模式下,GCC的表现也完全符合我的要求。


这是一个“最小”示例:

#include <cstddef>
#include <utility>
#include <functional>

template<typename>
class TExprBase;

template<std::size_t,typename,typename>
class TExprSistema;

template<std::size_t,typename>
class TExprUnaria;

template<std::size_t,typename>
class TExprBinaria;

template<std::size_t,std::size_t>
class TCampo;

template<typename T>
class TExprBase {};

template<std::size_t d,std::size_t r1,template<std::size_t,typename...> typename T>
class TExprBase<T<d,TRest...>>
{
public:
  template<typename... URest,typename...> typename U>
  TExprBinaria<d,U<d,URest...>,std::plus<>>
  operator +(TExprBase<U<d,rhs}; }

  template<typename... URest,std::minus<>>
  operator -(TExprBase<U<d,rhs}; }

  TExprUnaria<d,std::negate<>>
  operator -() const
    { return {*this}; }

  template<std::size_t r2,typename... URest,r1 + r2,r2,std::multiplies<>>
  operator *(TExprBase<U<d,rhs}; }

  TExprBinaria<d,std::multiplies<>>
  operator *(double const rhs) const
    { return {*this,0u,std::divides<>>
  operator /(TExprBase<U<d,std::divides<>>
  operator /(double const rhs) const
    { return {*this,typename...> typename U>
  TExprSistema<d,URest...>>
  operator ==(TExprBase<U<d,rhs}; }

  operator T<d,TRest...> const &() const
    { return *static_cast<T<d,TRest...> const *>(this); }
};

template<std::size_t d,std::size_t r,typename T,typename U>
class TExprSistema : public TExprBase<TExprSistema<d,r,T,U>>
{
private:
  TExprBase<T> const &lhs;
  TExprBase<U> const &rhs;

public:
  TExprSistema() = delete;

  TExprSistema(TExprBase<T> const &lhs_,TExprBase<U> const &rhs_) :
    lhs(lhs_),rhs(rhs_) {}

  TExprSistema(TExprBase<T> const &lhs_,U const &rhs_) :
    lhs(lhs_),rhs(rhs_) {}
};

template<std::size_t d,typename TOp>
class TExprUnaria : public TExprBase<TExprUnaria<d,TOp>>
{
private:
  T const &rhs;
  [[no_unique_address]] TOp const Op = {};

public:
  TExprUnaria(T const &rhs_) :
    rhs(rhs_) {}
};

template<std::size_t d,typename U,typename TOp>
class TExprBinaria : public TExprBase<TExprBinaria<d,U,TOp>>
{
private:
  T const &lhs;
  U const &rhs;
  [[no_unique_address]] TOp const Op = {};

public:
  TExprBinaria(T const &lhs_,std::size_t r>
class TCampo : public TExprBase<TCampo<d,r>> {};

template<std::size_t d,typename T>
class TExprDiv : public TExprBase<TExprDiv<d,T>> {};

template<std::size_t d,std::size_t r>
class TExprGrad : public TExprBase<TExprGrad<d,typename T>
class TExprLap : public TExprBase<TExprLap<d,typename...> typename T>
TExprDiv<d,r - 1u,1u,TRest...>>
inline div(TExprBinaria<d,TCampo<d,r - 1u>,std::multiplies<>> const &)
  { return {}; }

template<std::size_t d,std::size_t r>
TExprGrad<d,r + 1u>
inline grad(TCampo<d,r> const &)
  { return {}; }

template<std::size_t d,std::size_t r>
TExprLap<d,void>
inline lap(TCampo<d,std::size_t r>
class TSistema
{
public:
  template<typename T,typename U>
  TSistema(TExprSistema<d,U> const &);

  void
  Solve() const;

  void
  friend solve(TSistema const &Sistema)
    { Sistema.solve(); }
};

template<std::size_t d,typename U>
void
inline solve(TExprSistema<d,U> const &Expr)
  { solve(TSistema(Expr)); }

int main()
{
  TCampo<3u,1u> U;
  TCampo<3u,0u> nu,p;

  solve(div(U * U) - nu * lap(U) == -grad(p));

  return 0;
}

解决方法

我将严格按照以下步骤简化您的示例:

template <typename T>
struct Other { };

template <typename T>
struct Base {
    template <typename U>
    void operator==(Base<U> const&) const;

    template <typename U>
    void operator==(Other<U> const&) const;
};

struct A : Base<A> { };
template <typename T> struct B : Base<B<T>> { };

基本上,您拥有的是一些CRTP基类模板,该模板可与任何其他类似的东西以及任何Other<U>媲美。我为此选择了最简单的非bool返回类型,即... void

问题就变成了:上述工作有效吗?比较AB<T>Other<U>的每一种组合都行得通吗?


答案是:它可能会满足您的要求。

A{}B<int>{}B<double>{}B<char>{}进行比较。在这种情况下,我们既有真实的候选者(从左手边)也有重写的候选者(从右手边),并且这两个候选者都涉及到两者 em>参数,因此首选非重写候选者。

即使我们直接在任一方向上将Base<K>A比较,也是如此。这是一个对称比较。


其他类型更有趣。

在该方向上将A{}Other<T>{}进行比较是可以的。我们像在C ++ 17中那样调用成员operator==,这是唯一的候选者,并且它执行您期望的工作。

在C ++ 17中,由于Other<T>{}A{}的比较(即,在另一个方向)比较不正确,因为没有任何候选。但是由于不同的原因,它在C ++ 20中格式错误。现在我们有一个候选:反向A{} == Other<T>{}候选。但是由于[over.match.oper]/9,我们最终拒绝了候选人:

如果通过重载决议为operator==选择了重写的operator @候选者,则其返回类型应为cv bool,并且[...]

请注意,bool要求的返回类型在过载解析的 end 中起作用(如果非返回bool的重写候选者获胜,那是不正确的-而不是开始处(如您在评论中所暗示的那样,暗示不返回bool的函数甚至不被视为重写候选对象)。

因此,我们确实考虑了该候选者(这是我们唯一的候选者),但是由于它是无效的重写候选者,因此我们拒绝了。结果,它在C ++ 20中仍然像在C ++ 17中那样格式错误。由于其他原因,它现在的格式不正确,因此您将收到不同的错误消息。例如,C给我:

error: return type 'void' of selected 'operator==' function for rewritten '==' comparison is not 'bool'
    b == a;
    ~ ^  ~

而在C ++ 17中它将给出:

error: invalid operands to binary expression ('Other<int>' and 'A')
    b == a;
    ~ ^  ~