检查类是否具有给定签名的成员函数
我要求一个模板技巧来检测一个类是否具有给定签名的特定成员函数。
该问题与此处引用的问题类似 http://www.gotw.ca/gotw/071.htm 但不一样:在 Sutter 的书中,他回答了这样一个问题:C 类必须提供具有特定签名的成员函数,否则程序将无法编译。在我的问题中,如果一个类具有该功能,我需要做一些事情,否则做“其他事情”。
boost::serialization 也面临着类似的问题,但我不喜欢他们采用的解决方案:模板函数默认调用具有特定签名的自由函数(您必须定义),除非您定义了特定的成员函数(在他们的情况下“序列化”需要给定类型的2个参数)和特定的签名,否则会发生编译错误。即同时实现侵入式和非侵入式序列化。
我不喜欢这个解决方案有两个原因:
- 为了非侵入性,您必须覆盖 boost::serialization 命名空间中的全局“序列化”函数,因此您可以在您的客户端代码中打开命名空间 boost 和命名空间序列化!
- 堆栈来解决这个问题 混乱的是 10 到 12 次函数调用。
没有该成员函数的类定义一种自定义行为,并且我的实体位于不同的命名空间内(并且我不想在另一个命名空间中覆盖一个命名空间中定义的全局函数)
我需要为 我有解决这个难题的提示吗?
I'm asking for a template trick to detect if a class has a specific member function of a given signature.
The problem is similar to the one cited here
http://www.gotw.ca/gotw/071.htm
but not the same: in the item of Sutter's book he answered to the question that a class C MUST PROVIDE a member function with a particular signature, else the program won't compile. In my problem I need to do something if a class has that function, else do "something else".
A similar problem was faced by boost::serialization but I don't like the solution they adopted: a template function that invokes by default a free function (that you have to define) with a particular signature unless you define a particular member function (in their case "serialize" that takes 2 parameters of a given type) with a particular signature, else a compile error will happens. That is to implement both intrusive and non-intrusive serialization.
I don't like that solution for two reasons:
- To be non intrusive you must override the global "serialize" function that is in boost::serialization namespace, so you have IN YOUR CLIENT CODE to open namespace boost and namespace serialization!
- The stack to resolve that
mess was 10 to 12 function invocations.
I need to define a custom behavior for classes that has not that member function, and my entities are inside different namespaces (and I don't want to override a global function defined in one namespace while I'm in another one)
Can you give me a hint to solve this puzzle?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
这是依赖 C++11 功能的可能实现。即使函数是继承的,它也能正确检测到该函数(与已接受答案中的解决方案不同,正如 Mike Kinghan 在他的答案中观察到的那样) 。
此代码段测试的函数称为
serialize
:用法:
Here's a possible implementation relying on C++11 features. It correctly detects the function even if it's inherited (unlike the solution in the accepted answer, as Mike Kinghan observes in his answer).
The function this snippet tests for is called
serialize
:Usage:
我不确定我是否理解正确,但您可以利用 SFINAE 在编译时检测函数是否存在。我的代码示例(测试类是否具有成员函数 size_tused_memory() const)。
I'm not sure if I understand you correctly, but you may exploit SFINAE to detect function presence at compile-time. Example from my code (tests if class has member function size_t used_memory() const).
编译时成员函数问题的公认答案
内省虽然很受欢迎,但也有一个可以观察到的障碍
在以下程序中:
使用 GCC 4.6.3 构建,程序输出
110
- 通知我们T = std::shared_ptr
确实不提供int & T::operator*() const
。如果您还不明白这个问题,那么请看一下 的定义
标头
中的std::shared_ptr
将会阐明。在那实现,
std::shared_ptr
派生自基类它从中继承
operator*() const
。所以模板实例化SFINAE
构成“查找”运算符U = std::shared_ptr
不会发生,因为std::shared_ptr
没有operator*()
本身并没有模板实例化“继承”。
这个障碍不会影响众所周知的 SFINAE 方法,即使用“The sizeof() Trick”,
仅用于检测
T
是否具有某些成员函数mf
(参见例如这个答案和评论)。但
确定
T::mf
存在通常(通常?)不够好:您可能还需要确定它具有所需的签名。那就是
插图技术分数。所需签名的指针变体
铭刻在模板类型的参数中,该参数必须满足
&T::mf
使 SFINAE 探测成功。但是这个模板实例化当继承
T::mf
时,技术会给出错误的答案。用于 T::mf 编译时自省的安全 SFINAE 技术必须避免
在模板参数中使用
&T::mf
来实例化 SFINAE 所基于的类型函数模板分辨率取决于。相反,SFINAE 模板函数
解析只能取决于所使用的完全相关的类型声明
作为重载 SFINAE 探测函数的参数类型。
通过回答遵守此约束的问题,我将
说明
ET::operator*() const
的编译时检测,对于任意
T
和E
。相同的模式将适用必要的修改探测任何其他成员方法签名。
在此解决方案中,重载的 SFINAE 探针函数
test()
被“调用”递归地”。(当然,它实际上根本没有被调用;它只是
由编译器解析的假设调用的返回类型。)
我们需要探查至少一点、最多两点信息:
T::operator*()
是否存在?如果没有,我们就完成了。T::operator*()
存在, 是它的签名ET::operator*() const
?我们通过评估单个调用的返回类型来得到答案
到
测试(0,0)
。这是通过以下方式完成的:此调用可能会解析为
/* SFINAE operator-exists :) */
重载test()
,或者它可能会解析为/* SFINAE game over :( */
重载。它无法解析为
/* SFINAE operator-has- Correct-sig :) */
重载,因为那个人只需要一个参数,而我们正在传递两个参数。
为什么我们会超过两个?只是为了强制决议排除
/* SFINAE 操作符-有-正确-sig :) */
。第二个论点没有其他意义。对
test(0,0)
的调用将解析为/* SFINAE operator-exists :) */
如果第一个参数 0 满足该重载的第一个参数类型,
这是
decltype(&A::operator*)
,其中A = T
。 0 将满足该类型以防万一
T::operator*
存在。假设编译器对此表示“是”。然后它会随着
/* SFINAE 运算符存在:) */
并且它需要确定返回类型函数调用,在这种情况下是 decltype(test(&A::operator*)) -
再次调用
test()
的返回类型。这一次,我们只传递一个参数
&A::operator*
,我们现在将其传递给知道存在,否则我们就不会在这里。调用
test(&A::operator*)
可能会解析为
/* SFINAE operator-has- Correct-sig :) */
或再次解析为可能会解析为
/* SFINAE game over :( */
。调用将匹配/* SFINAE operator-has- Correct-sig :) */
以防万一&A::operator*
满足该重载的单个参数类型,即
E (A::*)() const
,与
A = T
。如果
T::operator*
具有所需的签名,编译器会在这里说“是”,然后再次必须评估重载的返回类型。不再
现在“递归”:它是
std::true_type
。如果编译器没有选择
/* SFINAE operator-exists :) */
作为调用
test(0,0)
或不选择/* SFINAE operator-has- Correct-sig :) */
对于调用
test(&A::operator*)
,那么无论哪种情况,它都会伴随/* SFINAE game over :( */
并且最终返回类型是std::false_type
。这是一个测试程序,显示了生成预期结果的模板
不同案例样本中的答案(再次是 GCC 4.6.3)。
这个想法有新的缺陷吗?它可以变得更通用吗?
遇到它避免的障碍?
The accepted answer to this question of compiletime member-function
introspection, although it is justly popular, has a snag which can be observed
in the following program:
Built with GCC 4.6.3, the program outputs
110
- informing us thatT = std::shared_ptr<int>
does not provideint & T::operator*() const
.If you are not already wise to this gotcha, then a look at of the definition of
std::shared_ptr<T>
in the header<memory>
will shed light. In thatimplementation,
std::shared_ptr<T>
is derived from a base classfrom which it inherits
operator*() const
. So the template instantiationSFINAE<U, &U::operator*>
that constitutes "finding" the operator forU = std::shared_ptr<T>
will not happen, becausestd::shared_ptr<T>
has nooperator*()
in its own right and template instantiation does not"do inheritance".
This snag does not affect the well-known SFINAE approach, using "The sizeof() Trick",
for detecting merely whether
T
has some member functionmf
(see e.g.this answer and comments). But
establishing that
T::mf
exists is often (usually?) not good enough: you mayalso need to establish that it has a desired signature. That is where the
illustrated technique scores. The pointerized variant of the desired signature
is inscribed in a parameter of a template type that must be satisfied by
&T::mf
for the SFINAE probe to succeed. But this template instantiatingtechnique gives the wrong answer when
T::mf
is inherited.A safe SFINAE technique for compiletime introspection of
T::mf
must avoid theuse of
&T::mf
within a template argument to instantiate a type upon which SFINAEfunction template resolution depends. Instead, SFINAE template function
resolution can depend only upon exactly pertinent type declarations used
as argument types of the overloaded SFINAE probe function.
By way of an answer to the question that abides by this constraint I'll
illustrate for compiletime detection of
E T::operator*() const
, forarbitrary
T
andE
. The same pattern will apply mutatis mutandisto probe for any other member method signature.
In this solution, the overloaded SFINAE probe function
test()
is "invokedrecursively". (Of course it isn't actually invoked at all; it merely has
the return types of hypothetical invocations resolved by the compiler.)
We need to probe for at least one and at most two points of information:
T::operator*()
exist at all? If not, we're done.T::operator*()
exists, is its signatureE T::operator*() const
?We get the answers by evaluating the return type of a single call
to
test(0,0)
. That's done by:This call might be resolved to the
/* SFINAE operator-exists :) */
overloadof
test()
, or it might resolve to the/* SFINAE game over :( */
overload.It can't resolve to the
/* SFINAE operator-has-correct-sig :) */
overload,because that one expects just one argument and we are passing two.
Why are we passing two? Simply to force the resolution to exclude
/* SFINAE operator-has-correct-sig :) */
. The second argument has no other signifance.This call to
test(0,0)
will resolve to/* SFINAE operator-exists :) */
justin case the first argument 0 satifies the first parameter type of that overload,
which is
decltype(&A::operator*)
, withA = T
. 0 will satisfy that typejust in case
T::operator*
exists.Let's suppose the compiler say's Yes to that. Then it's going with
/* SFINAE operator-exists :) */
and it needs to determine the return type ofthe function call, which in that case is
decltype(test(&A::operator*))
-the return type of yet another call to
test()
.This time, we're passing just one argument,
&A::operator*
, which we nowknow exists, or we wouldn't be here. A call to
test(&A::operator*)
mightresolve either to
/* SFINAE operator-has-correct-sig :) */
or again tomight resolve to
/* SFINAE game over :( */
. The call will match/* SFINAE operator-has-correct-sig :) */
just in case&A::operator*
satisfiesthe single parameter type of that overload, which is
E (A::*)() const
,with
A = T
.The compiler will say Yes here if
T::operator*
has that desired signature,and then again has to evaluate the return type of the overload. No more
"recursions" now: it is
std::true_type
.If the compiler does not choose
/* SFINAE operator-exists :) */
for thecall
test(0,0)
or does not choose/* SFINAE operator-has-correct-sig :) */
for the call
test(&A::operator*)
, then in either case it goes with/* SFINAE game over :( */
and the final return type isstd::false_type
.Here is a test program that shows the template producing the expected
answers in varied sample of cases (GCC 4.6.3 again).
Are there new flaws in this idea? Can it be made more generic without once again
falling foul of the snag it avoids?
以下是一些使用片段:
*所有这一切的核心是
检查给定类中的成员
x
。可以是 var、func、class、union 或 enum:检查成员函数
void x()
:检查成员变量
x
:检查成员类
x
:检查成员联合
x
:检查成员枚举
x
:检查任何成员函数
x
,无论签名如何:或
详细信息和核心:
宏(El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK
:
CREATE_MEMBER_ENUM_CHECK: CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
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:Check for member function
void x()
:Check for member variable
x
:Check for member class
x
:Check for member union
x
:Check for member enum
x
:Check for any member function
x
regardless of signature:OR
Details and core:
Macros (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
使用 c++ 20 这变得更加简单。假设我们要测试类
T
是否有成员函数void T::resize(typename T::size_type)
。例如,std::vector就有这样的成员函数。然后,用法是
With c++ 20 this becomes much simpler. Say we want to test if a class
T
has a member functionvoid T::resize(typename T::size_type)
. For example,std::vector<U>
has such a member function. Then,and the usage is
如果您知道所需的成员函数的名称,这应该足够了。 (在这种情况下,如果没有成员函数,则函数 bla 无法实例化(编写一个无论如何都可以工作的函数是很困难的,因为缺乏函数部分专业化。您可能需要使用类模板)此外,enable 结构体(与enable_if类似)也可以根据您希望其作为成员的函数类型进行模板化。
This should be sufficient, if you know the name of the member function you are expecting. (In this case, the function bla fails to instantiate if there is no member function (writing one that works anyway is tough because there is a lack of function partial specialization. You may need to use class templates) Also, the enable struct (which is similar to enable_if) could also be templated on the type of function you want it to have as a member.
这是对迈克·金汉的回答的更简单的看法。这将检测继承的方法。它还会检查精确签名(与允许参数转换的 jrok 方法不同)。
可运行示例
Here is a simpler take on Mike Kinghan's answer. This will detect inherited methods. It will also check for the exact signature (unlike jrok's approach which allows argument conversions).
Runnable example
您似乎想要检测器惯用语。上述答案是适用于 C++11 或 C++14 的变体。
std::experimental
库的功能基本上可以实现此目的。重写上面的示例,可能是:如果您不能使用 std::experimental,则可以像这样制作一个基本版本:
由于 has_serialize_t 实际上是 std::true_type 或 std::false_type,因此可以通过以下方式使用它:任何常见的 SFINAE 习惯用法:
或者使用带有重载解析的调度:
You appear to want the detector idiom. The above answers are variations on this that work with C++11 or C++14.
The
std::experimental
library has features which do essentially this. Reworking an example from above, it might be:If you can't use std::experimental, a rudimentary version can be made like this:
Since has_serialize_t is really either std::true_type or std::false_type, it can be used via any of the common SFINAE idioms:
Or by using dispatch with overload resolution:
您可以使用 std::is_member_function_pointer
You can use std::is_member_function_pointer
我自己也遇到了同样的问题,发现这里提出的解决方案非常有趣......但需要一个解决方案:
找到另一个 线程 提出类似的内容,基于BOOST 讨论。
以下是将建议的解决方案概括为特征类的两个宏声明,遵循 boost::has_* 类。
这些宏扩展为具有以下原型的特征类:
那么可以用它来做什么典型用法呢?
Came with the same kind of problem myself, and found the proposed solutions in here very interesting... but had the requirement for a solution that:
Found another thread proposing something like this, based on a BOOST discussion.
Here is the generalisation of the proposed solution as two macros declaration for traits class, following the model of boost::has_* classes.
These macros expand to a traits class with the following prototype:
So what is the typical usage one can do out of this?
为了实现这一点,我们需要使用:
true_type
或false_type
true_type
重载,需要int
和false_type
重载期望利用可变参数:"重载解析中的省略号转换”true_type
函数的模板规范时,我们将使用declval
和decltype
使我们能够独立于方法之间的返回类型差异或重载来检测函数您可以查看此示例这里。但我也会在下面解释一下:
我想检查是否存在名为
test
的函数它采用可从int
转换的类型,那么我需要声明这两个函数:decltype(hasTest(0))::value
istrue
(请注意,无需创建特殊功能来处理void a::test()
重载,void a::test(int)
被接受)decltype(hasTest(0))::value
为true
(因为int
可转换为double
int b::test(double)
被接受,与返回类型无关)decltype(hasTest(0))::value
是false
(c
没有名为test
的方法来接受可从int
转换的类型,因此不接受)解决方案有两个缺点:
test()方法?
因此,重要的是这些函数必须在详细信息命名空间中声明,或者理想情况下,如果它们仅与类一起使用,则应由该类私有声明它们。为此,我编写了一个宏来帮助您抽象此信息:
您可以这样使用:
随后调用
details::test_int::value
或details::test_void
出于内联代码或元编程的目的,a>::value
将产生true
或false
。To accomplish this we'll need to use:
type_traits
header, we'll want to return atrue_type
orfalse_type
from our overloadstrue_type
overload expecting anint
and thefalse_type
overload expecting Variadic Parameters to exploit: "The lowest priority of the ellipsis conversion in overload resolution"true_type
function we will usedeclval
anddecltype
allowing us to detect the function independent of return type differences or overloads between methodsYou can see a live example of this here. But I'll also explain it below:
I want to check for the existence of a function named
test
which takes a type convertible fromint
, then I'd need to declare these two functions:decltype(hasTest<a>(0))::value
istrue
(Note there is no need to create special functionality to deal with thevoid a::test()
overload, thevoid a::test(int)
is accepted)decltype(hasTest<b>(0))::value
istrue
(Becauseint
is convertable todouble
int b::test(double)
is accepted, independent of return type)decltype(hasTest<c>(0))::value
isfalse
(c
does not have a method namedtest
that accepts a type convertible fromint
therefor this is not accepted)This solution has 2 drawbacks:
test()
method?So it's important that these functions be declared in a details namespace, or ideally if they are only to be used with a class, they should be declared privately by that class. To that end I've written a macro to help you abstract this information:
You could use this like:
Subsequently calling
details::test_int<a>::value
ordetails::test_void<a>::value
would yieldtrue
orfalse
for the purposes of inline code or meta-programming.为了实现非侵入性,您还可以将
serialize
放在正在序列化的类或存档类的命名空间中,这要归功于 Koenig 查找。请参阅免费函数覆盖的命名空间了解更多详情。 :-)开放任何给定的命名空间来实现自由功能是完全错误的。 (例如,您不应该打开命名空间
std
来为您自己的类型实现swap
,而应该使用 Koenig 查找。)To be non-intrusive, you can also put
serialize
in the namespace of the class being serialised, or of the archive class, thanks to Koenig lookup. See Namespaces for Free Function Overrides for more details. :-)Opening up any given namespace to implement a free function is Simply Wrong. (e.g., you're not supposed to open up namespace
std
to implementswap
for your own types, but should use Koenig lookup instead.)好的。第二次尝试。如果你也不喜欢这个也没关系,我正在寻找更多的想法。
赫伯·萨特 (Herb Sutter) 的文章谈到了特质。因此,您可以拥有一个默认实例化具有后备行为的特征类,并且对于存在成员函数的每个类,该特征类专门用于调用成员函数。我相信 Herb 的文章提到了一种执行此操作的技术,这样就不需要大量的复制和粘贴。
但就像我说的,也许您不希望涉及“标记”实现该成员的类的额外工作。在这种情况下,我正在寻找第三种解决方案......
Okay. Second try. It's okay if you don't like this one either, I'm looking for more ideas.
Herb Sutter's article talks about traits. So you can have a traits class whose default instantiation has the fallback behaviour, and for each class where your member function exists, then the traits class is specialised to invoke the member function. I believe Herb's article mentions a technique to do this so that it doesn't involve lots of copying and pasting.
Like I said, though, perhaps you don't want the extra work involved with "tagging" classes that do implement that member. In which case, I'm looking at a third solution....
我有类似的需求并遇到了这个SO。这里提出了许多有趣/强大的解决方案,尽管对于特定需求来说有点长:检测类是否具有具有精确签名的成员函数。所以我做了一些阅读/测试并提出了我可能感兴趣的版本。它检测:
具有精确的签名。由于我不需要捕获任何签名(这需要更复杂的解决方案),因此这个适合我。它基本上使用了enable_if_t。
输出 :
I had a similar need and came across o this SO. There are many interesting/powerful solutions proposed here, though it is a bit long for just a specific need : detect if a class has member function with a precise signature. So I did some reading/testing and came up with my version that could be of interest. It detect :
with a precise signature. Since I don't need to capture any signature (that'd require a more complicated solution), this one suites to me. It basically used enable_if_t.
Output :
如果您正在使用 facebook folly,则有开箱即用的宏可以帮助您:
虽然实现细节与之前的答案相同,但使用库更简单。
If you are using facebook folly, there are out of box macro to help you:
Though the implementation details is the same with the previous answer, use a library is simpler.
如果没有 C++11 支持 (
decltype
),这可能会起作用:SSCCE
它如何工作
A
、Aa
和B
是有问题的类,Aa
是继承我们正在寻找的成员的特殊类。在
FooFinder
中,true_type
和false_type
是相应 C++11 类的替代品。另外,为了理解模板元编程,它们揭示了 SFINAE-sizeof-trick 的基础。TypeSink
是一个模板结构体,稍后用于将sizeof
运算符的积分结果放入模板实例化中以形成类型。match
函数是另一种 SFINAE 类型的模板,没有通用的对应项。因此,只有当其参数的类型与其专用的类型相匹配时,它才能被实例化。test
函数与枚举声明一起最终形成了中心 SFINAE 模式。有一个通用的使用省略号返回false_type
和一个具有更具体参数的对应项以优先。为了能够使用
T
模板参数实例化test
函数,必须实例化match
函数,因为它的返回类型需要实例化 TypeSink 参数。需要注意的是,包装在函数参数中的&U::foo
不会在模板参数专门化中引用,因此仍然会进行继承成员查找。Without C++11 support (
decltype
) this might work:SSCCE
How it hopefully works
A
,Aa
andB
are the clases in question,Aa
being the special one that inherits the member we're looking for.In the
FooFinder
thetrue_type
andfalse_type
are the replacements for the correspondent C++11 classes. Also for the understanding of template meta programming, they reveal the very basis of the SFINAE-sizeof-trick.The
TypeSink
is a template struct that is used later to sink the integral result of thesizeof
operator into a template instantiation to form a type.The
match
function is another SFINAE kind of template that is left without a generic counterpart. It can hence only be instantiated if the type of its argument matches the type it was specialized for.Both the
test
functions together with the enum declaration finally form the central SFINAE pattern. There is a generic one using an ellipsis that returns thefalse_type
and a counterpart with more specific arguments to take precedence.To be able to instantiate the
test
function with a template argument ofT
, thematch
function must be instantiated, as its return type is required to instantiate theTypeSink
argument. The caveat is that&U::foo
, being wrapped in a function argument, is not referred to from within a template argument specialization, so inherited member lookup still takes place.基于 jrok 的 答案,我避免使用嵌套模板类和/或函数。
我们可以按如下方式使用上述宏:
欢迎提出建议。
Building on jrok's answer, I have avoided using nested template classes and/or functions.
We can use the above macros as below:
Suggestions are welcome.