【发布时间】:2013-06-19 21:05:28
【问题描述】:
有一种(看似)不错的 C++03 方法可以确定一个类型是否具有成员函数或运算符:
https://github.com/jaredhoberock/is_call_possible/blob/master/is_call_possible.hpp
没有现代 C++11 方法可以做到这一点吗?最好不要包含任何外部代码而只使用标准。
【问题讨论】:
-
这个问题是有目的的还是你只是好奇
有一种(看似)不错的 C++03 方法可以确定一个类型是否具有成员函数或运算符:
https://github.com/jaredhoberock/is_call_possible/blob/master/is_call_possible.hpp
没有现代 C++11 方法可以做到这一点吗?最好不要包含任何外部代码而只使用标准。
【问题讨论】:
这适用于 GitHub 中给出的所有测试用例(演示:http://ideone.com/ZLGp4R):
#include <type_traits>
template <typename C, typename F, typename = void>
struct is_call_possible : public std::false_type {};
template <typename C, typename R, typename... A>
struct is_call_possible<C, R(A...),
typename std::enable_if<
std::is_same<R, void>::value ||
std::is_convertible<decltype(
std::declval<C>().operator()(std::declval<A>()...)
// ^^^^^^^^^^ replace this with the member you need.
), R>::value
>::type
> : public std::true_type {};
【讨论】:
C++ 11 增加了一个新技巧,我经常开玩笑地称之为“CFINAE”(编译失败不是错误)。
它利用decltype 运算符和SFINAE 的常规属性。
考虑以下函数:
template <typename X, typename Y>
static auto check(X& x, Y& y) -> decltype(x >> y);
仅当 X 和 Y 是
移位运算符已定义。为check 添加一个常规的包罗万象的重载,您就有了一种机制来测试是否可以编译任意表达式。
事实上,这是 Andrew Sutton(Concepts Lite 提案的作者之一)在实验性的 Origin 库中开发的原理。事实上,我的示例直接取自 here 以实现 Streamable 概念。
我推荐 Andrew Sutton 和 Bjarne Stroustrup 在 GoingNative 2012 上的以下演示文稿,他们介绍了新概念和 Origin 库:
http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/A-Concept-Design-for-C-
【讨论】:
不,几乎是一样的。或多或少。实现会有所不同,尽管您可以用标准库特征替换该实现内部使用的一些元函数。但是没有简单的方法来检测你是否可以在给定一组参数的情况下调用某种类型的函数。
这是给concepts (PDF)的。
【讨论】:
我使用以下基于 SFINAE 的众所周知的方法:
#define TYPE_SUPPORTS(ClassName, Expr) \
template<typename U> \
struct ClassName \
{ \
private: \
template<typename> \
static constexpr std::false_type test(...); \
\
template<typename T = U> \
static decltype((Expr), std::true_type{}) test(int) ; \
\
public: \
static constexpr bool value = decltype(test<U>(0))::value; \
};
宏的主要目的是简化添加类型检查。该宏定义了一个结构,允许您对T 类型进行任意检查。
例如,检查std::begin() 是否可以为一个类型调用:
namespace detail
{
TYPE_SUPPORTS(SupportsBegin, std::begin(std::declval<T>()))
}
template<typename T>
constexpr bool supportsBegin()
{
return detail::SupportsBegin<T>::value;
}
当然,detail 命名空间和函数包装器都是语法糖,但在调用者方面稍微改进了语法。
【讨论】: