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?
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
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?
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.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....
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 that implementation,std::shared_ptr<T>
is derived from a base class from which it inheritsoperator*() 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 thatT::mf
exists is often (usually?) not good enough: you may also 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 instantiating technique gives the wrong answer whenT::mf
is inherited.A safe SFINAE technique for compiletime introspection of
T::mf
must avoid the use of&T::mf
within a template argument to instantiate a type upon which SFINAE function 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
, for arbitraryT
andE
. The same pattern will apply mutatis mutandis to probe for any other member method signature.In this solution, the overloaded SFINAE probe function
test()
is "invoked recursively". (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 :) */
overload oftest()
, 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 :) */
just in case the first argument 0 satifies the first parameter type of that overload, which isdecltype(&A::operator*)
, withA = T
. 0 will satisfy that type just in caseT::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 of the function call, which in that case isdecltype(test(&A::operator*))
- the return type of yet another call totest()
.This time, we're passing just one argument,
&A::operator*
, which we now know exists, or we wouldn't be here. A call totest(&A::operator*)
might resolve either to/* SFINAE operator-has-correct-sig :) */
or again to might resolve to/* SFINAE game over :( */
. The call will match/* SFINAE operator-has-correct-sig :) */
just in case&A::operator*
satisfies the single parameter type of that overload, which isE (A::*)() const
, withA = 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 isstd::true_type
.If the compiler does not choose
/* SFINAE operator-exists :) */
for the calltest(0,0)
or does not choose/* SFINAE operator-has-correct-sig :) */
for the calltest(&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?
You can use std::is_member_function_pointer