是否可以编写一个模板来更改行为,具体取决于是否在类上定义了某个成员函数?
这是我要写的一个简单示例:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
因此,如果class T
已定义了toString()
,则它将使用它;否则,它将使用它。 否则,事实并非如此。 我不知道该怎么做的神奇部分是“ FUNCTION_EXISTS”部分。
#1楼
这个解决方案怎么样?
#include <type_traits>
template <typename U, typename = void> struct hasToString : std::false_type { };
template <typename U>
struct hasToString<U,
typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };
#2楼
我修改了https://stackoverflow.com/a/264088/2712152中提供的解决方案,使其更加通用。 另外,由于它不使用任何新的C ++ 11功能,因此我们可以将其与旧的编译器一起使用,并且也应与msvc一起使用。 但是,由于编译器使用可变参数宏,因此它们应使C99能够使用它。
以下宏可用于检查特定类是否具有特定typedef。
/**
* @class : HAS_TYPEDEF
* @brief : This macro will be used to check if a class has a particular
* typedef or not.
* @param typedef_name : Name of Typedef
* @param name : Name of struct which is going to be run the test for
* the given particular typedef specified in typedef_name
*/
#define HAS_TYPEDEF(typedef_name, name) \
template <typename T> \
struct name { \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<typename _1::typedef_name>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
以下宏可用于检查特定类是否具有特定成员函数以及是否具有给定数量的参数。
/**
* @class : HAS_MEM_FUNC
* @brief : This macro will be used to check if a class has a particular
* member function implemented in the public section or not.
* @param func : Name of Member Function
* @param name : Name of struct which is going to be run the test for
* the given particular member function name specified in func
* @param return_type: Return type of the member function
* @param ellipsis(...) : Since this is macro should provide test case for every
* possible member function we use variadic macros to cover all possibilities
*/
#define HAS_MEM_FUNC(func, name, return_type, ...) \
template <typename T> \
struct name { \
typedef return_type (T::*Sign)(__VA_ARGS__); \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U, U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<Sign, &_1::func>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
我们可以使用上面的两个宏来对has_typedef和has_mem_func进行检查:
class A {
public:
typedef int check;
void check_function() {}
};
class B {
public:
void hello(int a, double b) {}
void hello() {}
};
HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);
int main() {
std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}
#3楼
这是我的版本,它通过任意Arity处理所有可能的成员函数重载,包括模板成员函数,可能还有默认参数。 当使用给定的arg类型对某个类类型进行成员函数调用时,它可以区分3种互斥的方案:(1)有效,或(2)含糊,或(3)不可行。 用法示例:
#include <string>
#include <vector>
HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)
struct test
{
void bar(int);
void bar(double);
void bar(int,double);
template < typename T >
typename std::enable_if< not std::is_integral<T>::value >::type
bar(const T&, int=0){}
template < typename T >
typename std::enable_if< std::is_integral<T>::value >::type
bar(const std::vector<T>&, T*){}
template < typename T >
int bar(const std::string&, int){}
};
现在您可以像这样使用它:
int main(int argc, const char * argv[])
{
static_assert( has_mem_bar<test>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");
static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");
return 0;
}
这是用c ++ 11编写的代码,但是,您可以轻松地(稍作调整)将其移植到具有typeof扩展名的非c ++ 11(例如gcc)。 您可以使用自己的宏替换HAS_MEM宏。
#pragma once
#if __cplusplus >= 201103
#include <utility>
#include <type_traits>
#define HAS_MEM(mem) \
\
template < typename T > \
struct has_mem_##mem \
{ \
struct yes {}; \
struct no {}; \
\
struct ambiguate_seed { char mem; }; \
template < typename U > struct ambiguate : U, ambiguate_seed {}; \
\
template < typename U, typename = decltype(&U::mem) > static constexpr no test(int); \
template < typename > static constexpr yes test(...); \
\
static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ; \
typedef std::integral_constant<bool,value> type; \
};
#define HAS_MEM_FUN_CALL(memfun) \
\
template < typename Signature > \
struct has_valid_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_valid_mem_fun_call_##memfun< T(Args...) > \
{ \
struct yes {}; \
struct no {}; \
\
template < typename U, bool = has_mem_##memfun<U>::value > \
struct impl \
{ \
template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
struct test_result { using type = yes; }; \
\
template < typename V > static constexpr typename test_result<V>::type test(int); \
template < typename > static constexpr no test(...); \
\
static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename U > \
struct impl<U,false> : std::false_type {}; \
\
static constexpr bool value = impl<T>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_ambiguous_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) > \
{ \
struct ambiguate_seed { void memfun(...); }; \
\
template < class U, bool = has_mem_##memfun<U>::value > \
struct ambiguate : U, ambiguate_seed \
{ \
using ambiguate_seed::memfun; \
using U::memfun; \
}; \
\
template < class U > \
struct ambiguate<U,false> : ambiguate_seed {}; \
\
static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_viable_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_viable_mem_fun_call_##memfun< T(Args...) > \
{ \
static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value \
or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_no_viable_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) > \
{ \
static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct result_of_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct result_of_mem_fun_call_##memfun< T(Args...) > \
{ \
using type = decltype(std::declval<T>().memfun(std::declval<Args>()...)); \
};
#endif
#4楼
C ++ 20- requires
表达式
随着C ++ 20的出现,概念和各种工具(例如requires
表达式)成为检查函数是否存在的内置方式。 使用它们,您可以按如下方式重写您的optionalToString
函数:
template<class T>
std::string optionalToString(T* obj)
{
constexpr bool has_toString = requires(const T& t) {
t.toString();
};
if constexpr (has_toString)
return obj->toString();
else
return "toString not defined";
}
C ++ 20之前的版本-检测工具包
N4502提出了将其纳入C ++ 17标准库的方法,可以用某种优雅的方式解决该问题。 而且,它刚刚被库基本原理TS v2接受。 它引入了一些元函数,包括std::is_detected
,可用于在其顶部轻松编写类型或函数检测元函数。 这是使用方法:
template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );
template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;
请注意,上面的示例未经测试。 该检测工具包在标准库中尚不可用,但是该建议包含完整的实现,如果您确实需要,可以轻松复制该实现。 if constexpr
它可以与C ++ 17功能配合if constexpr
:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}
助推汉娜
Boost.Hana显然基于此特定示例,并在其文档中提供了C ++ 14的解决方案,因此我将直接引用它:
[...] Hana提供了一个
is_valid
函数,可以将其与C ++ 14通用lambda结合使用,以更清晰地实现同一事物:auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });
这给我们留下了一个函数对象
has_toString
,该对象返回给定表达式在传递给它的参数上是否有效。 结果以IntegralConstant
形式返回,因此constexpr-ness在这里不是问题,因为无论如何该函数的结果都表示为类型。 现在,除了不那么冗长(这只是一个衬里!)之外,意图也更加清楚了。 其他好处是,has_toString
可以传递给更高阶的算法,也可以在函数范围内定义,因此无需使用实现细节来污染名称空间范围。
助推器
Boost.TTI是Boost 1.54.0中引入的另一种惯用的工具包来执行这种检查,尽管它不那么优雅。 对于您的示例,您将必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION
。 这是使用方法:
#include <boost/tti/has_member_function.hpp>
// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)
// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;
然后,您可以使用bool
创建SFINAE检查。
说明
宏BOOST_TTI_HAS_MEMBER_FUNCTION
生成元函数has_member_function_toString
,该函数将已检查的类型作为其第一个模板参数。 第二个模板参数与成员函数的返回类型相对应,以下参数与函数的参数类型相对应。 如果类T
具有成员函数std::string toString()
则成员value
包含true
。
另外, has_member_function_toString
可以将成员函数指针作为模板参数。 因此,可以用has_member_function_toString<std::string T::* ()>::value
替换has_member_function_toString<T, std::string>::value
has_member_function_toString<std::string T::* ()>::value
。
#5楼
如果“如果我使用X,它将编译吗?”,这是C ++ 11的通用问题解决方案。
template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
T,
type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};
特性has_to_string
使得has_to_string<T>::value
为true
,并且仅当T
具有在此上下文中可以用0参数调用的.toString
方法时。
接下来,我将使用标签分配:
namespace details {
template<class T>
std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
return obj->toString();
}
template<class T>
std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
return "toString not defined";
}
}
template<class T>
std::string optionalToString(T* obj) {
return details::optionalToString_helper( obj, has_to_string<T>{} );
}
它比复杂的SFINAE表达式更易于维护。
如果发现自己做很多事情,可以使用宏编写这些特征,但是它们相对简单(每行几行),因此可能不值得:
#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};
以上是创建一个宏MAKE_CODE_TRAIT
。 您为它传递所需特征的名称,以及一些可以测试类型T
代码。 从而:
MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )
创建上述特征类。
顺便说一句,以上技术是MS所谓的“表达式SFINAE”的一部分,而他们的2013编译器则相当失败。
请注意,在C ++ 1y中,以下语法是可能的:
template<class T>
std::string optionalToString(T* obj) {
return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
return obj.toString();
}) *compiled_else ([&]{
return "toString not defined";
});
}
这是一个内联编译条件分支,它滥用许多C ++功能。 这样做可能不值得,因为(代码内联)的好处不值得(几乎没人理解它的工作原理)的代价,但是上述解决方案的存在可能会引起人们的兴趣。
来源:oschina
链接:https://my.oschina.net/u/3797416/blog/3161224