C++ 模板检查类成员函数是否存在?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/257288/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Templated check for the existence of a class member function?
提问by andy
Is it possible to write a template that changes behavior depending on if a certain member function is defined on a class?
是否可以编写一个模板,根据类上是否定义了某个成员函数来改变行为?
Here's a simple example of what I would want to write:
这是我想写的一个简单的例子:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
So, if class T
has toString()
defined, then it uses it; otherwise, it doesn't. The magical part that I don't know how to do is the "FUNCTION_EXISTS" part.
因此,如果class T
已toString()
定义,则使用它;否则,它不会。我不知道该怎么做的神奇部分是“FUNCTION_EXISTS”部分。
采纳答案by Nicola Bonelli
Yes, with SFINAE you can check if a given class does provide a certain method. Here's the working code:
是的,使用 SFINAE,您可以检查给定的类是否确实提供了某种方法。这是工作代码:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };
template <typename C> static one test( typeof(&C::helloworld) ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}
I've just tested it with Linux and gcc 4.1/4.3. I don't know if it's portable to other platforms running different compilers.
我刚刚用 Linux 和 gcc 4.1/4.3 对其进行了测试。我不知道它是否可以移植到运行不同编译器的其他平台。
回答by Xeo
This question is old, but with C++11 we got a new way to check for a functions existence (or existence of any non-type member, really), relying on SFINAE again:
这个问题很老了,但是在 C++11 中,我们得到了一种新的方法来检查函数是否存在(或任何非类型成员的存在,真的),再次依赖 SFINAE:
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
Now onto some explanations. First thing, I use expression SFINAEto exclude the serialize(_imp)
functions from overload resolution, if the first expression inside decltype
isn't valid (aka, the function doesn't exist).
现在开始一些解释。首先,我使用表达式 SFINAEserialize(_imp)
从重载解析中排除函数,如果里面的第一个表达式decltype
无效(也就是函数不存在)。
The void()
is used to make the return type of all those functions void
.
本void()
是用来做的所有这些函数的返回类型void
。
The 0
argument is used to prefer the os << obj
overload if both are available (literal 0
is of type int
and as such the first overload is a better match).
如果两者都可用,则该0
参数用于首选os << obj
重载(文字0
是类型int
,因此第一个重载是更好的匹配)。
Now, you probably want a trait to check if a function exists. Luckily, it's easy to write that. Note, though, that you need to write a trait yourselffor every different function name you might want.
现在,您可能想要一个特征来检查函数是否存在。幸运的是,编写它很容易。但是请注意,您需要为您可能需要的每个不同的函数名称自己编写一个特征。
#include <type_traits>
template<class>
struct sfinae_true : std::true_type{};
namespace detail{
template<class T, class A0>
static auto test_stream(int)
-> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
template<class, class A0>
static auto test_stream(long) -> std::false_type;
} // detail::
template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};
And on to explanations. First, sfinae_true
is a helper type, and it basically amounts to the same as writing decltype(void(std::declval<T>().stream(a0)), std::true_type{})
. The advantage is simply that it's shorter.
Next, the struct has_stream : decltype(...)
inherits from either std::true_type
or std::false_type
in the end, depending on whether the decltype
check in test_stream
fails or not.
Last, std::declval
gives you a "value" of whatever type you pass, without you needing to know how you can construct it. Note that this is only possible inside an unevaluated context, such as decltype
, sizeof
and others.
并进行解释。首先,sfinae_true
是一个辅助类型,它基本上等同于 write decltype(void(std::declval<T>().stream(a0)), std::true_type{})
。优点很简单,它更短。
接下来,根据签入是否失败,struct has_stream : decltype(...)
继承自std::true_type
或std::false_type
最终。
最后,为您提供您传递的任何类型的“值”,而您无需知道如何构造它。请注意,这仅在未评估的上下文中才有可能,例如,等。decltype
test_stream
std::declval
decltype
sizeof
Note that decltype
is not necessarily needed, as sizeof
(and all unevaluated contexts) got that enhancement. It's just that decltype
already delivers a type and as such is just cleaner. Here's a sizeof
version of one of the overloads:
请注意,这decltype
不是必需的,因为sizeof
(以及所有未评估的上下文)得到了这种增强。只是decltype
已经提供了一种类型,因此更干净。这sizeof
是其中一个重载的一个版本:
template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
int(*)[sizeof((os << obj),0)] = 0)
{
os << obj;
}
The int
and long
parameters are still there for the same reason. The array pointer is used to provide a context where sizeof
can be used.
出于同样的原因,int
和long
参数仍然存在。数组指针用于提供sizeof
可以使用的上下文。
回答by Johannes Schaub - litb
C++ allows SFINAEto be used for this (notice that with C++11 features this is simplier because it supports extended SFINAE on nearly arbitrary expressions - the below was crafted to work with common C++03 compilers):
C++ 允许将SFINAE用于此(请注意,使用 C++11 特性,这更简单,因为它支持对几乎任意表达式的扩展 SFINAE - 以下是为与常见的 C++03 编译器一起工作而设计的):
#define HAS_MEM_FUNC(func, name) \
template<typename T, typename Sign> \
struct name { \
typedef char yes[1]; \
typedef char no [2]; \
template <typename U, U> struct type_check; \
template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
template <typename > static no &chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
the above template and macro tries to instantiate a template, giving it a member function pointer type, and the actual member function pointer. If the types to not fit, SFINAE causes the template to be ignored. Usage like this:
上面的模板和宏试图实例化一个模板,给它一个成员函数指针类型,以及实际的成员函数指针。如果类型不适合,SFINAE 会导致模板被忽略。用法如下:
HAS_MEM_FUNC(toString, has_to_string);
template<typename T> void
doSomething() {
if(has_to_string<T, std::string(T::*)()>::value) {
...
} else {
...
}
}
But note that you cannot just call that toString
function in that if branch. since the compiler will check for validity in both branches, that would fail for cases the function doesnt exist. One way is to use SFINAE once again (enable_if can be gotten from boost too):
但请注意,您不能只toString
在 if 分支中调用该函数。由于编译器将检查两个分支的有效性,因此在函数不存在的情况下会失败。一种方法是再次使用 SFINAE(enable_if 也可以从 boost 获得):
template<bool C, typename T = void>
struct enable_if {
typedef T type;
};
template<typename T>
struct enable_if<false, T> { };
HAS_MEM_FUNC(toString, has_to_string);
template<typename T>
typename enable_if<has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T has toString ... */
return t->toString();
}
template<typename T>
typename enable_if<!has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T doesnt have toString ... */
return "T::toString() does not exist.";
}
Have fun using it. The advantage of it is that it also works for overloaded member functions, and also for const member functions (remember using std::string(T::*)() const
as the member function pointer type then!).
玩得开心。它的优点是它也适用于重载的成员函数,也适用于 const 成员函数(记住 std::string(T::*)() const
用作成员函数指针类型!)。
回答by Morwenn
C++20 - requires
expressions
C++20 -requires
表达式
With C++20 come concepts and assorted tools such as requires
expressionswhich are a built-in way to check for a function existence. With them you could rewrite your optionalToString
function as follows:
C++20 带来了概念和各种工具,例如requires
表达式,它们是检查函数是否存在的内置方式。使用它们,您可以optionalToString
按如下方式重写您的函数:
template<class T>
std::string optionalToString(T* obj)
{
constexpr bool has_toString = requires(const T& t) {
t.toString();
};
if constexpr (has_toString)
return obj->toString();
else
return "toString not defined";
}
Pre-C++20 - Detection toolkit
Pre-C++20 - 检测工具包
N4502proposes a detection toolkit for inclusion into the C++17 standard library that eventually made it into the library fundamentals TS v2. It most likely won't ever get into the standard because it has been subsumed by requires
expressions since, but it still solves the problem in a somewhat elegant manner. The toolkit introduces some metafunctions, including std::is_detected
which can be used to easily write type or function detection metafunctions on the top of it. Here is how you could use it:
N4502提出了一个检测工具包,用于包含到 C++17 标准库中,最终使其成为库基础 TS v2。它很可能永远不会进入标准,因为它已被requires
表达式包含在内,但它仍然以某种优雅的方式解决了问题。该工具包引入了一些元函数,包括std::is_detected
可用于在其顶部轻松编写类型或函数检测元函数。以下是您可以如何使用它:
template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );
template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;
Note that the example above is untested. The detection toolkit is not available in standard libraries yet but the proposal contains a full implementation that you can easily copy if you really need it. It plays nice with the C++17 feature if constexpr
:
请注意,上面的示例未经测试。检测工具包在标准库中尚不可用,但该提案包含完整的实现,如果您确实需要,可以轻松复制。它与 C++17 特性配合得很好if constexpr
:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}
C++14 - Boost.Hana
C++14 - Boost.Hana
Boost.Hana apparently builds upon this specific example and provides a solution for C++14 in its documentation, so I'm going to quote it directly:
Boost.Hana 显然是建立在这个特定示例之上的,并在其文档中为 C++14 提供了解决方案,因此我将直接引用它:
[...] Hana provides a
is_valid
function that can be combined with C++14 generic lambdas to obtain a much cleaner implementation of the same thing:auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });
This leaves us with a function object
has_toString
which returns whether the given expression is valid on the argument we pass to it. The result is returned as anIntegralConstant
, so constexpr-ness is not an issue here because the result of the function is represented as a type anyway. Now, in addition to being less verbose (that's a one liner!), the intent is much clearer. Other benefits are the fact thathas_toString
can be passed to higher order algorithms and it can also be defined at function scope, so there is no need to pollute the namespace scope with implementation details.
[...] Hana 提供了一个
is_valid
函数,该函数可以与 C++14 泛型 lambdas 结合使用,以获得相同事物的更清晰的实现:auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });
这给我们留下了一个函数对象
has_toString
,它返回给定的表达式对我们传递给它的参数是否有效。结果作为 返回IntegralConstant
,因此 constexpr-ness 在这里不是问题,因为函数的结果无论如何都表示为类型。现在,除了不那么冗长(这是一个单行!),意图更加清晰。其他好处是has_toString
可以传递给高阶算法,也可以在函数范围内定义,因此不需要用实现细节污染命名空间范围。
Boost.TTI
Boost.TTI
Another somewhat idiomatic toolkit to perform such a check - even though less elegant - is Boost.TTI, introduced in Boost 1.54.0. For your example, you would have to use the macro BOOST_TTI_HAS_MEMBER_FUNCTION
. Here is how you could use it:
执行这种检查的另一个有点惯用的工具包 - 尽管不太优雅 - 是Boost.TTI,在 Boost 1.54.0 中引入。对于您的示例,您必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION
. 以下是您可以如何使用它:
#include <boost/tti/has_member_function.hpp>
// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)
// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;
Then, you could use the bool
to create a SFINAE check.
然后,您可以使用bool
来创建 SFINAE 检查。
Explanation
解释
The macro BOOST_TTI_HAS_MEMBER_FUNCTION
generates the metafunction has_member_function_toString
which takes the checked type as its first template parameter. The second template parameter corresponds to the return type of the member function, and the following parameters correspond to the types of the function's parameters. The member value
contains true
if the class T
has a member function std::string toString()
.
该宏BOOST_TTI_HAS_MEMBER_FUNCTION
生成将has_member_function_toString
检查类型作为其第一个模板参数的元函数。第二个模板参数对应成员函数的返回类型,后面的参数对应函数的参数类型。该成员value
包含true
该类T
是否具有成员函数std::string toString()
。
Alternatively, has_member_function_toString
can take a member function pointer as a template parameter. Therefore, it is possible to replace has_member_function_toString<T, std::string>::value
by has_member_function_toString<std::string T::* ()>::value
.
或者,has_member_function_toString
可以将成员函数指针作为模板参数。因此,它是可以更换has_member_function_toString<T, std::string>::value
的has_member_function_toString<std::string T::* ()>::value
。
回答by FireAphis
Though this question is two years old, I'll dare to add my answer. Hopefully it will clarify the previous, indisputably excellent, solution. I took the very helpful answers of Nicola Bonelli and Johannes Schaub and merged them into a solution that is, IMHO, more readable, clear and does not require the typeof
extension:
虽然这个问题已经两年了,但我敢于补充我的答案。希望它能澄清以前的,无可争议的优秀解决方案。我采用了 Nicola Bonelli 和 Johannes Schaub 的非常有用的答案,并将它们合并到一个解决方案中,即恕我直言,更易读、更清晰并且不需要typeof
扩展:
template <class Type>
class TypeHasToString
{
// This type won't compile if the second template parameter isn't of type T,
// so I can put a function pointer type in the first parameter and the function
// itself in the second thus checking that the function has a specific signature.
template <typename T, T> struct TypeCheck;
typedef char Yes;
typedef long No;
// A helper struct to hold the declaration of the function pointer.
// Change it if the function signature changes.
template <typename T> struct ToString
{
typedef void (T::*fptr)();
};
template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
template <typename T> static No HasToString(...);
public:
static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};
I checked it with gcc 4.1.2. The credit goes mainly to Nicola Bonelli and Johannes Schaub, so give them a vote up if my answer helps you :)
我用 gcc 4.1.2 检查过它。功劳主要归功于 Nicola Bonelli 和 Johannes Schaub,如果我的回答对您有帮助,请给他们投票:)
回答by Aaron McDaid
A simple solution for C++11:
C++11 的简单解决方案:
template<class T>
auto optionalToString(T* obj)
-> decltype( obj->toString() )
{
return obj->toString();
}
auto optionalToString(...) -> string
{
return "toString not defined";
}
Update, 3 years later: (and this is untested). To test for the existence, I think this will work:
更新,3 年后:(这是未经测试的)。为了测试是否存在,我认为这会起作用:
template<class T>
constexpr auto test_has_toString_method(T* obj)
-> decltype( obj->toString() , std::true_type{} )
{
return obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
return "toString not defined";
}
回答by Konrad Rudolph
This is what type traits are there for. Unfortunately, they have to be defined manually. In your case, imagine the following:
这就是类型特征的用途。不幸的是,它们必须手动定义。在您的情况下,请想象以下情况:
template <typename T>
struct response_trait {
static bool const has_tostring = false;
};
template <>
struct response_trait<your_type_with_tostring> {
static bool const has_tostring = true;
}
回答by akim
Well, this question has a long list of answers already, but I would like to emphasize the comment from Morwenn: there is a proposal for C++17 that makes it really much simpler. See N4502for details, but as a self-contained example consider the following.
嗯,这个问题已经有很长的答案列表了,但我想强调 Morwenn 的评论:有一个 C++17 的提议使它变得非常简单。有关详细信息,请参阅N4502,但作为独立示例,请考虑以下内容。
This part is the constant part, put it in a header.
这部分是常量部分,放在标题中。
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
then there is the variable part, where you specify what you are looking for (a type, a member type, a function, a member function etc.). In the case of the OP:
然后是变量部分,您可以在其中指定要查找的内容(类型、成员类型、函数、成员函数等)。在 OP 的情况下:
template <typename T>
using toString_t = decltype(std::declval<T>().toString());
template <typename T>
using has_toString = detect<T, toString_t>;
The following example, taken from N4502, shows a more elaborate probe:
以下示例取自N4502,显示了一个更复杂的探针:
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
Compared to the other implementations described above, this one is fairly simple: a reduced set of tools (void_t
and detect
) suffices, no need for hairy macros. Besides, it was reported (see N4502) that it is measurably more efficient (compile-time and compiler memory consumption) than previous approaches.
与上述其他实现相比,这个实现相当简单:一组减少的工具 (void_t
和detect
) 就足够了,不需要毛茸茸的宏。此外,据报道(参见N4502),它比以前的方法更有效(编译时和编译器内存消耗)。
Here is a live example. It works fine with Clang, but unfortunately, GCC versions before 5.1 followed a different interpretation of the C++11 standard which caused void_t
to not work as expected. Yakk already provided the work-around: use the following definition of void_t
(void_t in parameter list works but not as return type):
这是一个活生生的例子。它适用于 Clang,但不幸的是,5.1 之前的 GCC 版本遵循了对 C++11 标准的不同解释,这导致void_t
无法按预期工作。Yakk 已经提供了解决方法:使用以下定义void_t
(参数列表中的 void_t 有效但不作为返回类型):
#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif
回答by Yakk - Adam Nevraumont
This is a C++11 solution for the general problem if "If I did X, would it compile?"
如果“如果我做了 X,它会编译吗?”,这是一个针对一般问题的 C++11 解决方案。
template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
T,
type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};
Trait has_to_string
such that has_to_string<T>::value
is true
if and only if T
has a method .toString
that can be invoked with 0 arguments in this context.
性状has_to_string
使得has_to_string<T>::value
是true
当且仅当T
有一个方法.toString
可以与在此上下文0参数调用。
Next, I'd use tag dispatching:
接下来,我将使用标签调度:
namespace details {
template<class T>
std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
return obj->toString();
}
template<class T>
std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
return "toString not defined";
}
}
template<class T>
std::string optionalToString(T* obj) {
return details::optionalToString_helper( obj, has_to_string<T>{} );
}
which tends to be more maintainable than complex SFINAE expressions.
这往往比复杂的 SFINAE 表达式更易于维护。
You can write these traits with a macro if you find yourself doing it alot, but they are relatively simple (a few lines each) so maybe not worth it:
如果您发现自己经常这样做,您可以使用宏编写这些特征,但它们相对简单(每行几行),因此可能不值得:
#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};
what the above does is create a macro MAKE_CODE_TRAIT
. You pass it the name of the trait you want, and some code that can test the type T
. Thus:
上面所做的是创建一个宏MAKE_CODE_TRAIT
。您将所需特征的名称和一些可以测试类型的代码传递给它T
。因此:
MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )
creates the above traits class.
创建上述特征类。
As an aside, the above technique is part of what MS calls "expression SFINAE", and their 2013 compiler fails pretty hard.
顺便说一句,上述技术是 MS 所谓的“表达式 SFINAE”的一部分,并且他们的 2013 编译器非常失败。
Note that in C++1y the following syntax is possible:
请注意,在 C++1y 中,以下语法是可能的:
template<class T>
std::string optionalToString(T* obj) {
return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
return obj.toString();
}) *compiled_else ([&]{
return "toString not defined";
});
}
which is an inline compilation conditional branch that abuses lots of C++ features. Doing so is probably not worth it, as the benefit (of code being inline) is not worth the cost (of next to nobody understanding how it works), but the existence of that above solution may be of interest.
这是一个内联编译条件分支,它滥用了许多 C++ 特性。这样做可能不值得,因为(内联代码的)好处不值得(几乎没有人了解它是如何工作的),但上述解决方案的存在可能会引起人们的兴趣。
回答by Brett Rossier
Here are some usage snippets: *The guts for all this are farther down
以下是一些使用片段: *所有这些的胆量都在更远的地方
Check for member x
in a given class. Could be var, func, class, union, or enum:
检查x
给定类中的成员。可以是 var、func、class、union 或 enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Check for member function void x()
:
检查成员函数void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
Check for member variable x
:
检查成员变量x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Check for member class x
:
检查成员类x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Check for member union x
:
检查会员工会x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Check for member enum x
:
检查成员枚举x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Check for any member function x
regardless of signature:
x
无论签名如何,都检查任何成员函数:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
OR
或者
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
Details and core:
细节和核心:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
Macros (El Diablo!):
宏(暗黑破坏神!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
CREATE_MEMBER_CHECKS:
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)