Reputation: 1278
Is it possible to check whether a class has a certain member function overload from within a template member function?
The best similar problem I was able to find is this one: Is it possible to write a template to check for a function's existence? As I understand it, this doesn't apply in to the case of checking for overloads of functions.
Here a simplified example of how this would be applied:
struct A;
struct B;
class C
{
public:
template<typename T>
void doSomething(std::string asdf)
{
T data_structure;
/** some code */
if(OVERLOAD_EXISTS(manipulateStruct, T))
{
manipulateStruct(data_structure);
}
/** some more code */
}
private:
void manipulateStruct(B& b) {/** some different code */};
}
My question would be if some standard way exists to make the following usage of the code work:
int main(int argc, const char** argv)
{
C object;
object.doSomething<A>("hello");
object.doSomething<B>("world");
exit(0);
}
The only methods I could think of would be to simply create an emtpy overload of manipulateStruct
for struct A
. Otherwise the manipulation method could of course also be put into the structs to be manipulated, which would make SFINAE an option. Let's assume both of these to not be a possiblity here.
Is there any way to get code similar to the above one to work? Does something similar to OVERLOAD_EXISTS
exist, to let the compiler know when to add the manipulateStruct
part to the generated code? Or is there maybe some way clever way to make SFINAE work for this case?
Upvotes: 4
Views: 933
Reputation: 1457
Since C++11, you can use a mix of std::declval and decltype to test for the existence of a specific overload:
// If overload exists, gets its return type.
// Else compiler error
decltype(std::declval<C&>().manipulateStruct(std::declval<T&>()))
This can be used in a SFINAE construct:
class C {
public:
// implementation skipped
private:
// Declared inside class C to access its private member.
// Enable is just a fake argument to do SFINAE in specializations.
template<typename T, typename Enable=void>
struct can_manipulate;
}
template<typename T, typename Enable>
struct C::can_manipulate : std::false_type {};
// Implemented outside class C, because a complete definition of C is needed for the declval.
template<typename T>
struct C::can_manipulate<T,std::void_t<decltype(std::declval<C&>().manipulateStruct(std::declval<T&>()))>> : std::true_type {};
Here I am ignoring the return type of the overload using std::void_t (C++17, but C++11 alternatives should be possible). If you want to check the return type, you can pass it to std::is_same or std::is_assignable.
This can be done with constexpr if:
template<typename T>
void doSomething(std::string asdf) {
T data_structure;
if constexpr (can_manipulate<T>::value) {
manipulateStruct(data_structure);
}
}
The if constexpr
will make the compiler discards the statement-true
if the condition evaluates to false. Without the constexpr
, the compilation will require the function call inside the if
to be valid in all cases.
You can emulate the if constexpr
behaviour with SFINAE:
class C {
// previous implementation
private:
template<typename T, typename Enable=void>
struct manipulator;
}
template<typename T, typename Enable>
struct C::manipulator {
static void call(C&, T&) {
//no-op
}
};
// can_manipulate can be inlined and removed from the code
template<typename T>
struct C::manipulator<T, typename std::enable_if<C::can_manipulate<T>::value>::type> {
static void call(C& object, T& local) {
object.manipulateStruct(local);
}
};
Function body:
template<typename T>
T doSomething()
{
T data_structure;
// replace if-constexpr:
manipulator<T>::call(*this, data_structure);
}
Upvotes: 3