【问题标题】:Writing and checking your own concepts in c++用 C++ 编写和检查你自己的概念
【发布时间】:2015-08-07 18:08:38
【问题描述】:

我正在编写一个仅包含标头的 C++ 库,该库大量使用模板。现在我想添加一些概念检查来处理在模板参数中使用不正确类型时引发的编译时错误。

例如,我需要可以指向单个对象(例如 std::shared_ptr)的类指针对象的概念,可以指向数组(通过 operator[])但不能指向的类指针对象与指针算术一起使用(如 std::unique_ptr),以及可与指针算术等一起使用的指针。

由于概念仍然不是标准的并且编译器不支持,我需要自己实现这个。我知道 Boost Concept 库,但出于某种原因,我不想将它添加到依赖项中。

那么问题来了,如何实现对某些类型需求的检查呢?它是如何在 Boost 中实现的?在这种情况下常用哪些技术?

【问题讨论】:

  • 好消息。 GCC 概念分支合并到主干中。
  • 概念模拟通常有一两个基本原则,但基于这些原则如何实施概念检查存在很大差异。询问常用技术如何实现对于 SO 来说可能是一个过于宽泛的问题。
  • 为什么不描述这些原则之一或同时描述它们的优点/缺点和代码示例?

标签: c++ boost template-meta-programming c++-concepts


【解决方案1】:

自从我还在使用 C++11 以来,我自己也做过一些类似的事情。本质上,这样做的方法是大量使用 SFINAE 并熟悉所有这些内容:http://en.cppreference.com/w/cpp/types

最重要的概念检查可以说是enable_if:如果第一个模板参数是true,它是一个提供给定返回类型的模板,如果该参数是false,则导致替换失败:

//this one gets called only for pointers
template <typename T>
typename enable_if<is_pointer<T>::value, bool>::type do_stuff(T) {}

//this one gets called only for non-pointers
template <typename T>
typename enable_if<not is_pointer<T>::value, bool>::type do_stuff(T) {}

如果您不关心是否能够重载此类内容并且您喜欢可读的错误消息,则应改用static_assert

template <typename T>
class pointer_thingy {
    static_assert(is_pointer<T>::value, "T must be a pointer");
    //...
};

现在,进入更困难的部分:定义您自己的类似概念的模板事物。如果可能的话,最好的方法是根据上面链接中已经存在的标准来编写它们。但是,有时您想检查那里不可用的东西,例如,特定操作的可用性。在这种情况下,SFINAE 是您的朋友:

template <typename T>
class is_equality_comparable {
    template <typename U> static auto check(const U& u) -> typename std::conditional<
            std::is_convertible<decltype(u == u), bool>::value,
            std::true_type, std::false_type>::type;
    static std::false_type check(...);
public:
    static constexpr bool value = decltype(check(std::declval<T>()))::value;
};

这会检查特定类型是否定义了相等运算符 (operator==),以及它是否返回可用作 bool 的内容。但是,它是如何做到的需要一些解释:这个类所做的主要事情是定义一个永远不会被调用的check 方法,并通过计算check 的返回类型来生成正确的值。在底部,该类就是这样做的:当使用 T 类型的虚值调用时,它确定 check 的返回类型(通过 declval 生成以避免依赖于构造函数)。为了使其正常工作,提供了check 的两个重载:第一个是模板化的,第二个使用... 表示法以便接受任何参数并具有比第一个重载更低的选择优先级。第一个重载使用后缀返回类型,以便它可以引用其参数(这使得代码很多更清晰)并使用conditionaltrue_typefalse_type 之间进行选择,基于是否operator== 正确返回可以用作bool 的内容。如果operator== 不存在,第一个重载会导致替换失败,SFINAE 会确保将其从可能的重载列表中悄悄丢弃,这意味着对check 的假设调用会退回到第二个重载,它只是返回false_type.

当然,这只是我的做法;这是一种有效的方法,但我不确定 Boost 是否这样做,或者就此而言,是否是其他人的做法。如果您能够使用具有实际概念支持的较新版本的 C++,那么您绝对应该使用它:在其他不错的功能中,如果您做错了什么,您将能够获得可理解的错误消息,这不一定是什么您将摆脱上述方法。最后一点,如果你真的决定做这样的事情,严格的测试是至关重要的:当你的类已经在你的代码的其他地方使用时,很容易出错,而且很难弄清楚如何修复它。

【讨论】:

  • std::conditional&lt;.., true_type, false_type&gt; 可以缩短为std::integral_constant&lt;bool, ..&gt;。更一般地说,我认为应该注意的是,现在定义您所展示的新概念的方式可能被认为是相当冗长的(并且接近于 C++03 遗留)。展示这些技术当然已经足够好了,但与 boost.hana 的 auto has_name = is_valid([](auto&amp;&amp; x) -&gt; decltype((void)x.name) { }); 示例或 Walter E. Brown 的 void_t 技术相去甚远。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-24
  • 1970-01-01
  • 2018-05-23
  • 1970-01-01
  • 2020-11-29
相关资源
最近更新 更多