是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
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”部分。
我的观点是:在不为每一个都创建冗长的类型特征,或使用实验特性或长代码的情况下,普遍地确定某个东西是否可调用:
template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }
std::false_type isCallableImpl(...) { return {}; }
template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
return decltype(isCallableImpl(callable, declval<Args>()...)){};
}
用法:
constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);
下面是工作代码的示例。
template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());
template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
return obj->toString();
}
template <class T>
std::string optionalToString(const T* obj, long)
{
return "toString not defined";
}
int main()
{
A* a;
B* b;
std::cout << optionalToString(a, 0) << std::endl; // This is A
std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}
toStringFn<T>* = nullptr将启用带有额外int参数的函数,该函数的优先级高于使用0调用时需要很长时间的函数。
你可以对函数使用相同的原则,如果函数被实现,返回true。
template <typename T>
constexpr bool toStringExists(long)
{
return false;
}
template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
return true;
}
int main()
{
A* a;
B* b;
std::cout << toStringExists<A>(0) << std::endl; // true
std::cout << toStringExists<B>(0) << std::endl; // false
}
你可以跳过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++ 17中另一种实现方法(灵感来自boost:hana)。
该解决方案不需要has_something<T> SFINAE类型trait类。
解决方案
////////////////////////////////////////////
// has_member implementation
////////////////////////////////////////////
#include <type_traits>
template<typename T, typename F>
constexpr auto has_member_impl(F&& f) -> decltype(f(std::declval<T>()), true)
{
return true;
}
template<typename>
constexpr bool has_member_impl(...) { return false; }
#define has_member(T, EXPR) \
has_member_impl<T>( [](auto&& obj)->decltype(obj.EXPR){} )
Test
////////////////////////////////////////////
// Test
////////////////////////////////////////////
#include <iostream>
#include <string>
struct Example {
int Foo;
void Bar() {}
std::string toString() { return "Hello from Example::toString()!"; }
};
struct Example2 {
int X;
};
template<class T>
std::string optionalToString(T* obj)
{
if constexpr(has_member(T, toString()))
return obj->toString();
else
return "toString not defined";
}
int main() {
static_assert(has_member(Example, Foo),
"Example class must have Foo member");
static_assert(has_member(Example, Bar()),
"Example class must have Bar() member function");
static_assert(!has_member(Example, ZFoo),
"Example class must not have ZFoo member.");
static_assert(!has_member(Example, ZBar()),
"Example class must not have ZBar() member function");
Example e1;
Example2 e2;
std::cout << "e1: " << optionalToString(&e1) << "\n";
std::cout << "e1: " << optionalToString(&e2) << "\n";
}
虽然这个问题是两年前的事了,但我敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我采纳了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检查了它。
这主要归功于尼古拉·博内利和约翰内斯·绍布,如果我的回答对你有帮助,请给他们投票:)