是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
因此,如果类T定义了toString(),那么它就使用它;否则,它就不会。我不知道如何做的神奇部分是“FUNCTION_EXISTS”部分。
可能不像其他例子那么好,但这是我为c++ 11想出的。这适用于选择重载方法。
template <typename... Args>
struct Pack {};
#define Proxy(T) ((T &)(*(int *)(nullptr)))
template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
enum { value = false };
};
template <typename Class, typename... Args>
struct HasFoo<
Class,
Pack<Args...>,
decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
enum { value = true };
};
示例使用
struct Object
{
int foo(int n) { return n; }
#if SOME_CONDITION
int foo(int n, char c) { return n + c; }
#endif
};
template <bool has_foo_int_char>
struct Dispatcher;
template <>
struct Dispatcher<false>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n) + c;
}
};
template <>
struct Dispatcher<true>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n, c);
}
};
int runExample()
{
using Args = Pack<int, char>;
enum { has_overload = HasFoo<Object, Args>::value };
Object object;
return Dispatcher<has_overload>::exec(object, 100, 'a');
}
虽然这个问题是两年前的事了,但我敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我采纳了Nicola Bonelli和Johannes Schaub非常有用的答案,并将它们合并到一个解决方案中,恕我之言,这个解决方案更易于阅读,更清晰,不需要扩展类型:
template <class Type>
class TypeHasToString
{
// This type won't compile if the second template parameter isn't of type T,
// so I can put a function pointer type in the first parameter and the function
// itself in the second thus checking that the function has a specific signature.
template <typename T, T> struct TypeCheck;
typedef char Yes;
typedef long No;
// A helper struct to hold the declaration of the function pointer.
// Change it if the function signature changes.
template <typename T> struct ToString
{
typedef void (T::*fptr)();
};
template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
template <typename T> static No HasToString(...);
public:
static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};
我用gcc 4.1.2检查了它。
这主要归功于尼古拉·博内利和约翰内斯·绍布,如果我的回答对你有帮助,请给他们投票:)
你可以跳过c++ 14中所有的元编程,只需要从fit库中使用fit::条件来编写:
template<class T>
std::string optionalToString(T* x)
{
return fit::conditional(
[](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
[](auto*) { return "toString not defined"; }
)(x);
}
你也可以直接从lambdas中创建函数:
FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
[](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
[](auto*) -> std::string { return "toString not defined"; }
);
然而,如果你使用的编译器不支持泛型lambdas,你将不得不编写单独的函数对象:
struct withToString
{
template<class T>
auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
{
return obj->toString();
}
};
struct withoutToString
{
template<class T>
std::string operator()(T*) const
{
return "toString not defined";
}
};
FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
withToString(),
withoutToString()
);
可能不像其他例子那么好,但这是我为c++ 11想出的。这适用于选择重载方法。
template <typename... Args>
struct Pack {};
#define Proxy(T) ((T &)(*(int *)(nullptr)))
template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
enum { value = false };
};
template <typename Class, typename... Args>
struct HasFoo<
Class,
Pack<Args...>,
decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
enum { value = true };
};
示例使用
struct Object
{
int foo(int n) { return n; }
#if SOME_CONDITION
int foo(int n, char c) { return n + c; }
#endif
};
template <bool has_foo_int_char>
struct Dispatcher;
template <>
struct Dispatcher<false>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n) + c;
}
};
template <>
struct Dispatcher<true>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n, c);
}
};
int runExample()
{
using Args = Pack<int, char>;
enum { has_overload = HasFoo<Object, Args>::value };
Object object;
return Dispatcher<has_overload>::exec(object, 100, 'a');
}
c++ 11的一个简单解决方案:
template<class T>
auto optionalToString(T* obj)
-> decltype( obj->toString() )
{
return obj->toString();
}
auto optionalToString(...) -> string
{
return "toString not defined";
}
更新,3年后:(这是未经测试的)。为了检验是否存在,我认为这是可行的:
template<class T>
constexpr auto test_has_toString_method(T* obj)
-> decltype( obj->toString() , std::true_type{} )
{
return obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
return "toString not defined";
}