6

我有一个受强烈影响的有限体积库,它可以像在纸上一样用 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, template <std::size_t, std::size_t, typename...> typename U>
  SystemExpr<dim, rank, T<dim, rank, TRest...>, U<dim, rank, URest...>>
  operator ==(BaseExpr<U<dim, rank, URest...>> const &rhs) const
    { return {*this, rhs}; }

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

whereSystemExpr<dim, rank, T<dim, rank, TRest...>, U<dim, rank, 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, r1, TRest ...>, VF::TTensor<d, r> > VF::TExprBase<T<d, r1, TRest ...> >::operator==(const VF::TTensor<d, r>&) const [with long unsigned int d = 3; long unsigned int r1 = 0; TRest = {VF::TExprBinaria<3, 0, VF::TExprd<3, 0>, double, std::divides<void> >, VF::TExprBinaria<3, 0, double, VF::TExprLap<3, 0, 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, 0, VF::TExprBinaria<3, 0, VF::TExprd<3, 0>, double, std::divides<void> >, VF::TExprBinaria<3, 0, double, VF::TExprLap<3, 0, void>, std::multiplies<void> >, std::minus<void> >’

因此,即使在 C++20 模式下,GCC 的行为似乎也完全符合我的要求。


这是一个“最小”的例子:

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

template<typename>
class TExprBase;

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

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

template<std::size_t, std::size_t, typename, typename, 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, typename... TRest,
         template<std::size_t, std::size_t, typename...> typename T>
class TExprBase<T<d, r1, TRest...>>
{
public:
  template<typename... URest, template<std::size_t, std::size_t, typename...> typename U>
  TExprBinaria<d, r1, T<d, r1, TRest...>, U<d, r1, URest...>, std::plus<>>
  operator +(TExprBase<U<d, r1, URest...>> const &rhs) const
    { return {*this, rhs}; }

  template<typename... URest, template<std::size_t, std::size_t, typename...> typename U>
  TExprBinaria<d, r1, T<d, r1, TRest...>, U<d, r1, URest...>, std::minus<>>
  operator -(TExprBase<U<d, r1, URest...>> const &rhs) const
    { return {*this, rhs}; }

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

  template<std::size_t r2, typename... URest,
           template<std::size_t, std::size_t, typename...> typename U>
  TExprBinaria<d, r1 + r2, T<d, r1, TRest...>, U<d, r2, URest...>, std::multiplies<>>
  operator *(TExprBase<U<d, r2, URest...>> const &rhs) const
    { return {*this, rhs}; }

  TExprBinaria<d, r1, T<d, r1, TRest...>, double, std::multiplies<>>
  operator *(double const rhs) const
    { return {*this, rhs}; }

  template<typename... URest, template<std::size_t, std::size_t, typename...> typename U>
  TExprBinaria<d, r1, T<d, r1, TRest...>, U<d, 0u, URest...>, std::divides<>>
  operator /(TExprBase<U<d, 0u, URest...>> const &rhs) const
    { return {*this, rhs}; }

  TExprBinaria<d, r1, T<d, r1, TRest...>, double, std::divides<>>
  operator /(double const rhs) const
    { return {*this, rhs}; }

  template<typename... URest, template<std::size_t, std::size_t, typename...> typename U>
  TExprSistema<d, r1, T<d, r1, TRest...>, U<d, r1, URest...>>
  operator ==(TExprBase<U<d, r1, URest...>> const &rhs) const
    { return {*this, rhs}; }

  operator T<d, r1, TRest...> const &() const
    { return *static_cast<T<d, r1, 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, std::size_t r, typename T, typename TOp>
class TExprUnaria : public TExprBase<TExprUnaria<d, r, T, TOp>>
{
private:
  T const &rhs;
  [[no_unique_address]] TOp const Op = {};

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

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

public:
  TExprBinaria(T const &lhs_, U const &rhs_) :
    lhs(lhs_), rhs(rhs_) {}
};

template<std::size_t d, std::size_t r>
class TCampo : public TExprBase<TCampo<d, r>> {};

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

template<std::size_t d, std::size_t r>
class TExprGrad : public TExprBase<TExprGrad<d, r>> {};

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

template<std::size_t d, std::size_t r, typename... TRest,
         template<std::size_t, std::size_t, typename...> typename T>
TExprDiv<d, r - 1u, T<d, 1u, TRest...>>
inline div(TExprBinaria<d, r, T<d, 1u, TRest...>, 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, r, void>
inline lap(TCampo<d, r> const &)
  { return {}; }

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

  void
  Solve() const;

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

template<std::size_t d, std::size_t r, typename T, typename U>
void
inline solve(TExprSistema<d, r, T, 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;
}
4

1 回答 1

4

我将严重减少您的示例,如下所示:

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>{}

即使我们在任一方向上直接将 aBase<K>与a 进行比较也是如此A。这是一个对称的比较。


其他类型更有趣。

与那个方向相比很好A{}Other<T>{}我们像在 C++17 中那样调用该成员operator==,这是唯一的候选者,它会执行您所期望的操作。

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

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

请注意,要求的返回类型在重载决议结束bool时发挥作用(如果非返回的重写候选者获胜,则它的格式不正确)而不是在开始时(正如您在评论中提到的那样,暗示非-返回函数甚至不被视为重写候选者)。boolbool

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

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;
    ~ ^  ~
于 2020-10-31T17:07:42.717 回答