Question:
Is it possible in C++
to write something like this:
template<class T>
void f(T a) {
if ( Существет метод a.foo ) {
a.foo();
} else {
myfoo(a);
}
}
Those. the function should behave differently depending on whether class T has method foo
or not.
Answer:
This is done like this:
#include <iostream>
struct A{
void foo(){}
};
struct B{};
template<class T>
struct Test{
typedef void(T::*P)(void);
template<class U, P = &U::foo>
struct True{char dummy[2];};
typedef char False;
static False detect(...);
template<class U>
static True<U> detect(U*);
static const bool exists = (sizeof(False) != sizeof(detect(static_cast<T*>(0))));
};
int main(){
std::cout << std::boolalpha << Test<B>::exists << std::endl; //false
std::cout << std::boolalpha << Test<A>::exists << std::endl; //true
}
Now about what is going on here. The code uses an idiom called SFINAE. The abbreviation SFINAE stands for substitution failure is not an error and means the following: when defining function overloads, erroneous template instantiations do not cause a compilation error, but are discarded from the list of candidates for the most appropriate overload .
Now let's see how this code will behave. The Test
class defines two types True
and False
. Their important property is that sizeof(False) != sizeof(True<T>)
.
We also have two detect
methods at our disposal. The first takes a pointer to T
, the second takes an arbitrary number of arguments (an ellipsis). Ellipsis is omnivorous, and at the same time has the lowest priority when choosing an overload .
When detect(static_cast<T*>(0)
is called, the compiler tries to find an appropriate overload. The template version of detect
has higher priority. But if the type T
does not have a void foo()
method, then the template instantiation will fail. But as we remember, substitution failure is not an error The compiler will give preference to the ellipsis.
We can find out which version of detect
was chosen by the type of the return value. And we can distinguish types by their size.
Note that there is no implementation of the detect
methods in the code. It uses the fact that sizeof
does not evaluate the value of the expression, it immediately extracts the type . Thus the detect
methods will never be called.
As a result, the variable exists
will be true
if the type T
has a method void foo()
.
All code is executed at compile time and does not provide any overhead.
Also, this code was compiled by the online compiler in C++98 compatibility mode
UPD: I was hinted in the comments that this does not solve your problem. To some extent, this is true. The resulting value cannot simply be used in an if
, as this will result in a compilation error. We need an analogue of if
, which would work at the compilation stage. The role of such an if
can be template specialization:
template<class T, bool b = Test<T>::exists>
struct Foo;
template<class T>
struct Foo<T, true>{
static void foo(const T &t){
t.foo();
}
};
template<class T>
struct Foo<T, false>{
static void foo(const T &t){
std::cout << "foo" << std::endl;
}
};
The Foo
structure has two specializations. The first one is for the case Test<T>::exists == true
, the second one is for false
. Well, a small cherry on the cake for convenience, adding nothing new except automatic inference of the type of arguments:
template<class T>
void foo(const T &t){
Foo<T>::foo(t);
}
Now main
will look like this:
int main(){
A a;
B b;
foo(a); //A::foo
foo(b); //foo
}