C++17 下模仿 C++20 的 requires clause

出了一道让学生造个简单的 Polynomial 的题,主要是练习 class 以及 operator overloading。为了快速地检验学生的实现是否符合我们对各个成员函数的要求,C++20 concepts 可以发挥神力:

namespace detail {

template <typename T, typename... U>
concept any_of = (std::same_as<T, U> || ...);

} // namespace detail

template <typename T>
concept correct_polynomial
  = requires(T p, const T cp, std::size_t i, double x) {
      { p[i] } -> std::same_as<double &>;
      { cp[i] } -> detail::any_of<double, const double, const double &>;
      { cp(x) } -> detail::any_of<double, const double>;
      { -cp } -> detail::any_of<T, const T>;
      { cp + cp } -> detail::any_of<T, const T>;
      { cp - cp } -> detail::any_of<T, const T>;
      { cp * cp } -> detail::any_of<T, const T>;
      { p += cp } -> std::same_as<T &>;
      { p -= cp } -> std::same_as<T &>;
      { p *= cp } -> std::same_as<T &>;
      { cp.derivative() } -> detail::any_of<T, const T>;
      { cp.integral() } -> detail::any_of<T, const T>;
      { cp == cp } -> std::same_as<bool>;
      { cp != cp } -> std::same_as<bool>;
    };

static_assert(correct_polynomial<Polynomial>);

(此处省去了一些对构造、拷贝控制等成员的要求)

这种写法不仅简单清楚,而且能产生非常好的报错信息:

compile_test.cpp:65:15: error: static assertion failed
   65 | static_assert(correct_polynomial<Polynomial>);
      |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compile_test.cpp:65:15: note: constraints not satisfied
compile_test.cpp:41:9:   required by the constraints of ‘template<class T> concept correct_polynomial’
compile_test.cpp:47:10:   in requirements with ‘T p’, ‘const T cp’, ‘std::size_t i’, ‘double x’ [with T = Polynomial]
compile_test.cpp:57:11: note: ‘p -= cp’ does not satisfy return-type-requirement
   57 |         { p -= cp } -> std::same_as<T &>;

它可以明确地告诉我 { p -= cp } -> std::same_as<T &> 这一条没有满足,而不是一些 no match for call to function xxxx 之类的错误(虽然后者在报错点的上下文比较清晰的情况下也还算可读)。

遗憾的是我们课程是 based on C++17 的,这是出于多方面的考虑,在这里不多解释。想要在 C++17 里做到这个效果(即捕捉未满足的 requirements,并自定义报错信息)是可能的,但就需要 SFINAE:

namespace detail {

template <typename P = Polynomial, typename CP = const Polynomial,
          typename R = decltype(std::declval<P &>() -= std::declval<CP &>()),
          typename = std::enable_if_t<std::is_same_v<R, P &>>>
std::true_type helper(int);
std::false_type helper(...);

} // namespace detail

static_assert(decltype(detail::helper(0))::value,
              "Expect { p -= cp } -> Polynomial &");

这样写可以达到效果,但是也太费事了一点,关键是编译器在遇到 substitution failure 的时候并不告诉我具体是哪一个 substitution fail 了,而是悄无声息地转向另一个 overload candidate,于是我们也不能将所有 requirements 都写在一个 helper 里。此外,到处带着 std::declval<...>() 实在是麻烦。

仔细考虑之后我捣鼓出这么个玩意:

template <typename T, typename... Types>
using enable_if_any = std::enable_if_t<(std::is_same_v<T, Types> || ...)>;

#define EXPECT(NAME, EXPR, ...)                                                \
  template <typename P = Polynomial, typename CP = const Polynomial,           \
            typename R = decltype(EXPR),                                       \
            typename = enable_if_any<R, __VA_ARGS__>>                          \
  std::true_type test_##NAME(int);                                             \
  std::false_type test_##NAME(...);                                            \
  static_assert(decltype(test_##NAME(0))::value, \
                "Expect { " #EXPR " } -> any_of {" #__VA_ARGS__ "}");

#define p std::declval<P &>()
#define cp std::declval<CP &>()
#define i std::size_t{}
#define x double{}

EXPECT(subscript,       p[i],            double &)
EXPECT(const_subscript, cp[i],           double, const double, const double &)
EXPECT(evaluate,        cp(x),           double, const double)
EXPECT(negate,          -cp,             Polynomial, const Polynomial)
EXPECT(plus,            cp + cp,         Polynomial, const Polynomial)
EXPECT(minus,           cp - cp,         Polynomial, const Polynomial)
EXPECT(multiply,        cp * cp,         Polynomial, const Polynomial)
EXPECT(add_assign,      p += cp,         Polynomial &)
EXPECT(minus_assign,    p -= cp,         Polynomial &)
EXPECT(multiply_assign, p *= cp,         Polynomial &)
EXPECT(derivative,      cp.derivative(), Polynomial, const Polynomial)
EXPECT(integral,        cp.integral(),   Polynomial, const Polynomial)
EXPECT(equal,           cp == cp,        bool)
EXPECT(not_equal,       cp != cp,        bool)

#undef p
#undef cp
#undef i
#undef x
#undef EXPECT

还算能用吧。最好放进一个 namespace 里,避免造成名字空间污染。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值