Reputation: 1130
I want to create an overloaded template, that runs a function Foo() if a class contains it, else it does nothing.
class A
{
public:
template <typename U>
void Foo(U& u)
{
std::cout << "Has Foo()" << std::endl;
// Modify u
}
};
class B
{
// Does not contain Foo()
};
I've been trying to run it as such
template <typename T, typename U>
decltype(std::declval<T>().Foo()) TriggerFoo(T* t, U& u)
{
t->Foo(u);
}
template <typename T, typename U>
void TriggerFoo(T* t, U& u)
{
std::cout << "Does not have Foo()" << std::endl;
}
int main()
{
A a;
B b;
U u; // Some type
TriggerFoo<A, U>(&a, u); // I want to print "Has Foo()".
TriggerFoo<B, U>(&b, u); // Prints "Does not have Foo()".
return 0;
}
At the moment, both classes are passed to the "Does not have Foo()" instantiation. It compiles, but obviously it doesn't work, and it is most probably because I don't understand declval well enough. I have also tried with non-template functions and it still does not work.
Any help will be greatly appreciated.
Upvotes: 5
Views: 4687
Reputation:
Extension to Sam's answer, if you aren't using pointers you can simplify the code further which makes it look a bit neater.
#include <iostream>
#include <type_traits>
class A
{
public:
template <typename U>
void Foo(U& u)
{
std::cout << "Has Foo()\n";
}
};
class B
{
// Does not contain Foo()
};
template <
typename T,
typename U,
typename Z=decltype(std::declval<T>().Foo(std::declval<U&>()))>
void TriggerFoo(T& t, U& u)
{
t.Foo(u);
}
template <typename... T>
void TriggerFoo(const T&...)
{
std::cout << "Does not have Foo()\n";
}
class U {};
int main()
{
A a;
B b;
U u;
TriggerFoo<A, U>(a, u); // I want to print "Has Foo()".
TriggerFoo<B, U>(b, u); // Prints "Does not have Foo()".
return 0;
}
Upvotes: 5
Reputation: 118340
There were two basic issues with your approach:
decltype(std::declval<T>().Foo())
This will never be succesfully resolved, because the Foo()
in question will always take a parameter. This part should be:
decltype(std::declval<T>().Foo(std::declval<U &>()))
But now you will run into a different problem: when the class implements Foo()
, the template resolution will become ambiguous. Either template function can be used.
So, you need another level of indirection, and templates of different priorities:
#include <iostream>
#include <type_traits>
class A
{
public:
template <typename U>
void Foo(U& u)
{
std::cout << "Has Foo()" << std::endl;
// Modify u
}
};
class B
{
// Does not contain Foo()
};
template <typename T, typename U, typename Z=decltype(std::declval<T>().Foo(std::declval<U &>()))>
void DoTriggerFoo(T* t, U& u, int dummy)
{
t->Foo(u);
}
template <typename T, typename U>
void DoTriggerFoo(T* t, U& u, ...)
{
std::cout << "Does not have Foo()" << std::endl;
}
template <typename T, typename U>
void TriggerFoo(T *t, U &u)
{
DoTriggerFoo(t, u, 0);
}
class U {};
int main()
{
A a;
B b;
U u; // Some type
TriggerFoo<A, U>(&a, u); // I want to print "Has Foo()".
TriggerFoo<B, U>(&b, u); // Prints "Does not have Foo()".
return 0;
}
Results with gcc 5.3:
$ ./t
Has Foo()
Does not have Foo()
P.S.:
std::declval<T &>().Foo(std::declval<U &>())
It's possible that this will work better, with your actual classes.
Upvotes: 4