重写比较运算符和表达式模板
Rewritten comparison operators and expression templates
我有一个受 openfoam 强烈影响的有限体积库,它使连续介质力学问题的解决方案能够像用 C++ 编写一样
会在纸上。例如,要求解不可压缩层流的 Navier-Stokes 方程,我只需写:
solve(div(U * U) - nu * lap(U) == -grad(p));
这个表达式当然涉及表达式模板,因此系统系数是单次计算的,系统以无矩阵的方式求解。
这些表达式模板基于 CRTP,因此所有必要的运算符都在基础 class 中定义。特别是,相等运算符定义如下:
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}; }
};
其中 SystemExpr<dim, rank, T<dim, rank, TRest...>, U<dim, rank, URest...>>
具有惰性评估系数和计算解决方案的功能。
与 OpenFOAM 相比,在 OpenFOAM 中,您必须限定,例如,fvm::lap
、fvc::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;
}
我将按如下方式大幅减少您的示例:
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 基础 class 模板,它可以与任何其他类似的东西相媲美,也可以与任何 Other<U>
相媲美。我为这些选择了最简单的非 bool
return 类型,也就是说... void
.
那么问题就变成了:上面的方法行得通吗?比较 A
、B<T>
和 Other<U>
的每个组合都有效吗?
答案是:它可能会满足您的要求。
将 A{}
与 B<int>{}
或 B<double>{}
与 B<char>{}
进行比较就可以了。在这些场景中,我们有真正的候选者(来自 left-hand 端)和重写的候选者(来自 right-hand 端)并且两个候选者都涉及 derived-to-base 转换 两个参数,所以non-rewritten候选人是首选。
即使我们在任一方向直接比较 Base<K>
和 A
也是如此。这是一个对称比较。
其他类型更有趣。
在那个方向比较A{}
和Other<T>{}
就可以了。我们像在 C++17 中那样调用成员 operator==
,这是唯一的候选者,它会做你所期望的。
比较 Other<T>{}
和 A{}
(即,在另一个方向)在 C++17 中是 ill-formed,因为没有任何候选项。但出于不同的原因,它在 C++20 中是 ill-formed。现在我们确实有了一个候选者:反转的 A{} == Other<T>{}
候选者。但由于[over.match.oper]/9:
,我们最终拒绝了候选人
If a rewritten operator==
candidate is selected by overload resolution for an operator @
, its return type shall be cv bool
, and [...]
请注意,bool
要求的 return 类型在重载解析的 结束 开始发挥作用(如果非 bool
-returning 重写的候选人获胜,它是 ill-formed) 而不是在 beginning (正如你在评论中提到的,暗示非 bool
-returning 函数甚至不被视为重写候选者)。
所以我们确实考虑了这个候选人(这是我们唯一的候选人),但我们拒绝了,因为它是一个无效的重写候选人。结果,它在 C++20 中仍然是 ill-formed,就像在 C++17 中一样。只是现在 ill-formed 是出于不同的原因,因此您会收到不同的错误消息。例如,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;
~ ^ ~
我有一个受 openfoam 强烈影响的有限体积库,它使连续介质力学问题的解决方案能够像用 C++ 编写一样 会在纸上。例如,要求解不可压缩层流的 Navier-Stokes 方程,我只需写:
solve(div(U * U) - nu * lap(U) == -grad(p));
这个表达式当然涉及表达式模板,因此系统系数是单次计算的,系统以无矩阵的方式求解。
这些表达式模板基于 CRTP,因此所有必要的运算符都在基础 class 中定义。特别是,相等运算符定义如下:
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}; }
};
其中 SystemExpr<dim, rank, T<dim, rank, TRest...>, U<dim, rank, URest...>>
具有惰性评估系数和计算解决方案的功能。
与 OpenFOAM 相比,在 OpenFOAM 中,您必须限定,例如,fvm::lap
、fvc::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;
}
我将按如下方式大幅减少您的示例:
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 基础 class 模板,它可以与任何其他类似的东西相媲美,也可以与任何 Other<U>
相媲美。我为这些选择了最简单的非 bool
return 类型,也就是说... void
.
那么问题就变成了:上面的方法行得通吗?比较 A
、B<T>
和 Other<U>
的每个组合都有效吗?
答案是:它可能会满足您的要求。
将 A{}
与 B<int>{}
或 B<double>{}
与 B<char>{}
进行比较就可以了。在这些场景中,我们有真正的候选者(来自 left-hand 端)和重写的候选者(来自 right-hand 端)并且两个候选者都涉及 derived-to-base 转换 两个参数,所以non-rewritten候选人是首选。
即使我们在任一方向直接比较 Base<K>
和 A
也是如此。这是一个对称比较。
其他类型更有趣。
在那个方向比较A{}
和Other<T>{}
就可以了。我们像在 C++17 中那样调用成员 operator==
,这是唯一的候选者,它会做你所期望的。
比较 Other<T>{}
和 A{}
(即,在另一个方向)在 C++17 中是 ill-formed,因为没有任何候选项。但出于不同的原因,它在 C++20 中是 ill-formed。现在我们确实有了一个候选者:反转的 A{} == Other<T>{}
候选者。但由于[over.match.oper]/9:
If a rewritten
operator==
candidate is selected by overload resolution for anoperator @
, its return type shall be cvbool
, and [...]
请注意,bool
要求的 return 类型在重载解析的 结束 开始发挥作用(如果非 bool
-returning 重写的候选人获胜,它是 ill-formed) 而不是在 beginning (正如你在评论中提到的,暗示非 bool
-returning 函数甚至不被视为重写候选者)。
所以我们确实考虑了这个候选人(这是我们唯一的候选人),但我们拒绝了,因为它是一个无效的重写候选人。结果,它在 C++20 中仍然是 ill-formed,就像在 C++17 中一样。只是现在 ill-formed 是出于不同的原因,因此您会收到不同的错误消息。例如,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;
~ ^ ~