是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?
下面是我想写的一个简单的例子:
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”部分。
泛型模板,用于检查类型是否支持某些“特性”:
#include <type_traits>
template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
// these structs are used to recognize which version
// of the two functions was chosen during overload resolution
struct supported {};
struct not_supported {};
// this overload of chk will be ignored by SFINAE principle
// if TypeChecker<Type_> is invalid type
template <typename Type_>
static supported chk(typename std::decay<TypeChecker<Type_>>::type *);
// ellipsis has the lowest conversion rank, so this overload will be
// chosen during overload resolution only if the template overload above is ignored
template <typename Type_>
static not_supported chk(...);
// if the template overload of chk is chosen during
// overload resolution then the feature is supported
// if the ellipses overload is chosen the the feature is not supported
static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};
检查方法foo是否与signature double兼容的模板(const char*)
// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));
例子
// types that support has_foo
struct struct1 { double foo(const char*); }; // exact signature match
struct struct2 { int foo(const std::string &str); }; // compatible signature
struct struct3 { float foo(...); }; // compatible ellipsis signature
struct struct4 { template <typename T>
int foo(T t); }; // compatible template signature
// types that do not support has_foo
struct struct5 { void foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double foo( int *); }; // const char* can't be converted to int*
struct struct8 { double bar(const char*); }; // there is no foo method
int main()
{
std::cout << std::boolalpha;
std::cout << is_supported<has_foo, int >::value << std::endl; // false
std::cout << is_supported<has_foo, double >::value << std::endl; // false
std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
std::cout << is_supported<has_foo, struct4>::value << std::endl; // true
std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
std::cout << is_supported<has_foo, struct8>::value << std::endl; // false
return 0;
}
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
你可以跳过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()
);
是的,使用SFINAE您可以检查给定的类是否提供了特定的方法。下面是工作代码:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };
template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}
我刚刚用Linux和gcc 4.1/4.3测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。
我一直在寻找一个方法,允许以某种方式不绑定结构名has_member类的成员的名字。
实际上,如果lambda可以被允许在未求值的表达式中(这是被标准禁止的),这将更简单,即has_member<ClassName, SOME_MACRO_WITH_DECLTYPE(member_name)>
#include <iostream>
#include <list>
#include <type_traits>
#define LAMBDA_FOR_MEMBER_NAME(NAME) [](auto object_instance) -> decltype(&(decltype(object_instance)::NAME)) {}
template<typename T>
struct TypeGetter
{
constexpr TypeGetter() = default;
constexpr TypeGetter(T) {}
using type = T;
constexpr auto getValue()
{
return std::declval<type>();
}
};
template<typename T, typename LambdaExpressionT>
struct has_member {
using lambda_prototype = LambdaExpressionT;
//SFINAE
template<class ValueT, class = void>
struct is_void_t_deducable : std::false_type {};
template<class ValueT>
struct is_void_t_deducable<ValueT,
std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))>> : std::true_type {};
static constexpr bool value = is_void_t_deducable<T>::value;
};
struct SimpleClass
{
int field;
void method() {}
};
int main(void)
{
const auto helpful_lambda = LAMBDA_FOR_MEMBER_NAME(field);
using member_field = decltype(helpful_lambda);
std::cout << has_member<SimpleClass, member_field>::value;
const auto lambda = LAMBDA_FOR_MEMBER_NAME(method);
using member_method = decltype(lambda);
std::cout << has_member<SimpleClass, member_method>::value;
}
如果方法恰好定义在基类中,那么这里由litb提供的标准c++解决方案将不能像预期的那样工作。
处理这种情况的解决方案请参考:
俄语:
http://www.rsdn.ru/forum/message/2759773.1.aspx
由罗马人翻译的英文。Perepelitsa:
http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
它非常聪明。然而,这种解决方案的一个问题是,如果被测试的类型不能用作基类(例如基本类型),则会给出编译器错误。
在Visual Studio中,我注意到如果使用没有参数的方法,则需要在参数周围插入一对额外的冗余()来在sizeof表达式中推导()。