自从我仍在使用 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
的返回类型来生成正确的值。在底部,该类就是这样做的:它确定使用类型check
的虚构值调用时的返回类型T
(通过生成declval
以避免依赖于构造函数)。为了使其正常工作,check
提供了两个重载:第一个是模板化的,第二个使用...
符号,以便接受任何参数并具有比第一个重载更低的选择优先级。第一个重载使用后缀返回类型,以便它可以引用其参数(这使代码更清晰)并用于根据conditional
是否正确返回可以用作. 如果不存在,第一个重载会导致替换失败,并且 SFINAE 会确保将其从可能的重载列表中悄悄丢弃,这意味着假设调用回退到第二个重载,它只是返回。true_type
false_type
operator==
bool
operator==
check
false_type
当然,这只是我的做法;这是一种有效的方法,但我不确定 Boost 是否这样做,或者就此而言,是否是其他人的做法。如果您能够使用具有实际概念支持的较新版本的 C++,那么您绝对应该使用它:在其他不错的功能中,如果您做错了什么,您将能够获得可理解的错误消息,这不一定是什么您将摆脱上述方法。最后一点,如果你真的决定做这样的事情,严格的测试是至关重要的:当你的类已经在代码的其他地方使用时,很容易出错,而且很难弄清楚如何修复它。