and*_*ndy 458 c++ templates sfinae template-meta-programming
是否可以编写一个模板来改变行为,具体取决于是否在类上定义了某个成员函数?
这是我想写的一个简单例子:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
Run Code Online (Sandbox Code Playgroud)
所以,如果class T
已经toString()
确定的话,就使用它; 否则,它没有.我不知道怎么做的神奇部分是"FUNCTION_EXISTS"部分.
Nic*_*lli 304
是的,使用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( typeof(&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;
}
Run Code Online (Sandbox Code Playgroud)
我刚用Linux和gcc 4.1/4.3测试过它.我不知道它是否可以移植到运行不同编译器的其他平台.
Xeo*_*Xeo 257
这个问题很老,但是使用C++ 11,我们有了一种新方法来检查函数是否存在(或者确实存在任何非类型成员),再次依赖SFINAE:
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
Run Code Online (Sandbox Code Playgroud)
现在进行一些解释.首先,如果内部的第一个表达式无效(也就是说,函数不存在),我使用表达式SFINAEserialize(_imp)
从重载解析中排除函数decltype
.
本void()
是用来做的所有这些函数的返回类型void
.
如果两者都可用,则该0
参数用于优先选择重载os << obj
(文字0
是类型的int
,因此第一个重载是更好的匹配).
现在,您可能需要一个特征来检查函数是否存在.幸运的是,写起来很容易.但是请注意,您需要为自己想要的每个不同的函数名自己编写一个特征.
#include <type_traits>
template<class>
struct sfinae_true : std::true_type{};
namespace detail{
template<class T, class A0>
static auto test_stream(int)
-> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
template<class, class A0>
static auto test_stream(long) -> std::false_type;
} // detail::
template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};
Run Code Online (Sandbox Code Playgroud)
并解释.首先,sfinae_true
是一个帮助器类型,它基本上与写入相同decltype(void(std::declval<T>().stream(a0)), std::true_type{})
.优点是它更短.
接下来,取决于签入是否失败,struct has_stream : decltype(...)
从任一端std::true_type
或std::false_type
最后继承.
最后,为您提供所传递的任何类型的"值",而无需您知道如何构建它.请注意,这只能在未评估的上下文中使用,例如,和其他.decltype
test_stream
std::declval
decltype
sizeof
请注意,decltype
不一定需要,因为sizeof
(并且所有未评估的上下文)都获得了增强.它只是decltype
已经提供了一种类型,因此只是更清洁.这是一个sizeof
重载的版本:
template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
int(*)[sizeof((os << obj),0)] = 0)
{
os << obj;
}
Run Code Online (Sandbox Code Playgroud)
由于同样的原因,int
和long
参数仍然存在.数组指针用于提供sizeof
可以使用的上下文.
Joh*_*itb 159
C++允许SFINAE用于此(请注意,使用C++ 11功能时这更简单,因为它支持几乎任意表达式上的扩展SFINAE - 以下是为了与常见的C++ 03编译器一起工作):
#define HAS_MEM_FUNC(func, name) \
template<typename T, typename Sign> \
struct name { \
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); \
}
Run Code Online (Sandbox Code Playgroud)
上面的模板和宏试图实例化一个模板,给它一个成员函数指针类型,以及实际的成员函数指针.如果类型不合适,SFINAE会导致模板被忽略.用法如下:
HAS_MEM_FUNC(toString, has_to_string);
template<typename T> void
doSomething() {
if(has_to_string<T, std::string(T::*)()>::value) {
...
} else {
...
}
}
Run Code Online (Sandbox Code Playgroud)
但请注意,您不能只toString
在分支中调用该函数.因为编译器将在两个分支中检查有效性,否则在函数不存在的情况下将失败.一种方法是再次使用SFINAE(enable_if也可以从boost获得):
template<bool C, typename T = void>
struct enable_if {
typedef T type;
};
template<typename T>
struct enable_if<false, T> { };
HAS_MEM_FUNC(toString, has_to_string);
template<typename T>
typename enable_if<has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T has toString ... */
return t->toString();
}
template<typename T>
typename enable_if<!has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T doesnt have toString ... */
return "T::toString() does not exist.";
}
Run Code Online (Sandbox Code Playgroud)
玩得开心.它的优点是它也适用于重载的成员函数,也适用于const成员函数(请记住使用 std::string(T::*)() const
作为成员函数的指针类型!).
Mor*_*enn 63
N4502提出了一种包含在C++ 17标准库中的检测功能,它可以以一种优雅的方式解决问题.而且,它刚被接受进入库基础TS v2.它介绍了一些元函数,包括requires
可以用来轻松编写类型或函数检测元函数的元函数.以下是如何使用它:
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";
}
Run Code Online (Sandbox Code Playgroud)
请注意,上面的示例未经测试.检测工具包尚未在标准库中提供,但该提案包含一个完整的实现,如果您真的需要它可以轻松复制.它与C++ 17功能相得益彰requires
:
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>;
Run Code Online (Sandbox Code Playgroud)
另一个有点惯用的工具包来执行这样的检查 - 尽管不那么优雅 - 是Boost.TTI,在Boost 1.54.0中引入.对于您的示例,您必须使用宏optionalToString
.以下是如何使用它:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用它std::is_detected
来创建SFINAE检查.
说明
宏if constexpr
生成元函数is_valid
,该元函数将检查类型作为其第一个模板参数.第二个模板参数对应于成员函数的返回类型,以下参数对应于函数参数的类型.该成员has_toString
包含IntegralConstant
该类has_toString
是否具有成员函数BOOST_TTI_HAS_MEMBER_FUNCTION
.
或者,bool
可以将成员函数指针作为模板参数.因此,可以替换BOOST_TTI_HAS_MEMBER_FUNCTION
为has_member_function_toString
.
Fir*_*his 54
虽然这个问题已经有两年了,但我还是敢补充一下.希望它能澄清以前无可争议的优秀解决方案.我采用了Nicola Bonelli和Johannes Schaub的非常有用的答案,并将它们合并为一个解决方案,即恕我直言,更易读,更清晰,不需要typeof
扩展:
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));
};
Run Code Online (Sandbox Code Playgroud)
我用gcc 4.1.2检查了它.这个功劳主要归功于Nicola Bonelli和Johannes Schaub,所以如果我的回答可以帮助你,请给他们一个投票:)
Aar*_*aid 30
C++ 11的简单解决方案:
template<class T>
auto optionalToString(T* obj)
-> decltype( obj->toString() )
{
return obj->toString();
}
auto optionalToString(...) -> string
{
return "toString not defined";
}
Run Code Online (Sandbox Code Playgroud)
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";
}
Run Code Online (Sandbox Code Playgroud)
Kon*_*lph 29
这是什么类型的特征.不幸的是,它们必须手动定义.在您的情况下,请想象以下内容:
template <typename T>
struct response_trait {
static bool const has_tostring = false;
};
template <>
struct response_trait<your_type_with_tostring> {
static bool const has_tostring = true;
}
Run Code Online (Sandbox Code Playgroud)
aki*_*kim 22
好吧,这个问题已经有很多答案了,但我想强调一下Morwenn的评论:有一个C++ 17的提案让它变得非常简单.有关详细信息,请参阅N4502,但作为一个独立的示例,请考虑以下内容.
这部分是常量部分,将其放在标题中.
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
Run Code Online (Sandbox Code Playgroud)
然后是变量部分,您可以在其中指定要查找的内容(类型,成员类型,函数,成员函数等).在OP的情况下:
template <typename T>
using toString_t = decltype(std::declval<T>().toString());
template <typename T>
using has_toString = detect<T, toString_t>;
Run Code Online (Sandbox Code Playgroud)
以下示例取自N4502,显示了更精细的探测:
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
Run Code Online (Sandbox Code Playgroud)
与上面描述的其他实现相比,这个实现相当简单:减少了一组工具(void_t
和detect
)就足够了,不需要毛茸茸的宏.此外,据报道(见N4502),它比以前的方法更有效(编译时和编译器内存消耗).
这是一个实例.它与Clang一起工作正常,但不幸的是,5.1之前的GCC版本遵循对C++ 11标准的不同解释,导致void_t
无法按预期工作.Yakk已经提供了解决方法:使用以下定义void_t
(参数列表中的void_t工作但不作为返回类型):
#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif
Run Code Online (Sandbox Code Playgroud)
Jea*_*ier 17
这是我在 C++20 中找到的最简洁的方法,它与您的问题非常接近:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (requires { obj->toString(); })
return obj->toString();
else
return "toString not defined";
}
Run Code Online (Sandbox Code Playgroud)
在 godbolt 上观看直播:https://gcc.godbolt.org/z/5jb1d93Ms
Bre*_*ier 10
以下是一些使用片段:*所有这些的胆量更远
检查x
给定类中的成员.可以是var,func,class,union或enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
检查会员功能void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
检查成员变量x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
检查会员类x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
检查成员联盟x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
检查成员枚举x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
检查任何成员函数,x
无论签名如何:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
要么
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)
细节和核心:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
Run Code Online (Sandbox Code Playgroud)
宏(El Diablo!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
Run Code Online (Sandbox Code Playgroud)
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
Run Code Online (Sandbox Code Playgroud)
Yak*_*ont 10
对于一般问题,这是一个C++ 11解决方案,如果"如果我做了X,它会编译吗?"
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 {};
Run Code Online (Sandbox Code Playgroud)
性状has_to_string
使得has_to_string<T>::value
是true
当且仅当T
有一个方法.toString
可以与在此上下文0参数调用.
接下来,我将使用标签调度:
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>{} );
}
Run Code Online (Sandbox Code Playgroud)
这比复杂的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 {};
Run Code Online (Sandbox Code Playgroud)
上面做的是创建一个宏MAKE_CODE_TRAIT
.你传递了你想要的特征的名称,以及一些可以测试类型的代码T
.从而:
MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )
Run Code Online (Sandbox Code Playgroud)
创建上述特征类.
顺便说一句,上述技术是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";
});
}
Run Code Online (Sandbox Code Playgroud)
这是一个滥用大量C++特性的内联编译条件分支.这样做可能是不值得的,因为(代码是内联的)的好处不值得花费(没有人理解它是如何工作的),但是上述解决方案的存在可能是有意义的.
现在这是一个不错的小谜题 - 很棒的问题!
这是Nicola Bonelli解决方案的替代方案,不依赖于非标准typeof
操作员.
遗憾的是,它不适用于GCC(MinGW)3.4.5或Digital Mars 8.42n,但它适用于所有版本的MSVC(包括VC6)和Comeau C++.
较长的注释块具有关于它如何工作(或应该工作)的详细信息.正如它所说,我不确定哪种行为符合标准 - 我欢迎对此进行评论.
更新 - 2008年11月7日:
看起来虽然这段代码在语法上是正确的,但MSVC和Comeau C++显示的行为并不符合标准(感谢Leon Timmermans和litb指向我正确的方向).C++ 03标准说明如下:
14.6.2从属名称[temp.dep]
第3段
在类模板的定义或类模板的成员中,如果类模板的基类依赖于模板参数,则在类的定义时,在非限定名称查找期间不会检查基类作用域.模板或成员或在类模板或成员的实例化期间.
因此,看起来当MSVC或Comeau考虑在实例化模板时在调用站点执行名称查找的toString()
成员函数时,这是不正确的(即使它实际上是我在这种情况下寻找的行为).T
doToString()
GCC和数字火星的行为看起来是正确的 - 在这两种情况下,非成员toString()
函数都绑定到调用.
老鼠 - 我以为我可能找到了一个聪明的解决方案,而是发现了一些编译器错误......
#include <iostream>
#include <string>
struct Hello
{
std::string toString() {
return "Hello";
}
};
struct Generic {};
// the following namespace keeps the toString() method out of
// most everything - except the other stuff in this
// compilation unit
namespace {
std::string toString()
{
return "toString not defined";
}
template <typename T>
class optionalToStringImpl : public T
{
public:
std::string doToString() {
// in theory, the name lookup for this call to
// toString() should find the toString() in
// the base class T if one exists, but if one
// doesn't exist in the base class, it'll
// find the free toString() function in
// the private namespace.
//
// This theory works for MSVC (all versions
// from VC6 to VC9) and Comeau C++, but
// does not work with MinGW 3.4.5 or
// Digital Mars 8.42n
//
// I'm honestly not sure what the standard says
// is the correct behavior here - it's sort
// of like ADL (Argument Dependent Lookup -
// also known as Koenig Lookup) but without
// arguments (except the implied "this" pointer)
return toString();
}
};
}
template <typename T>
std::string optionalToString(T & obj)
{
// ugly, hacky cast...
optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);
return temp->doToString();
}
int
main(int argc, char *argv[])
{
Hello helloObj;
Generic genericObj;
std::cout << optionalToString( helloObj) << std::endl;
std::cout << optionalToString( genericObj) << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用 C++ 20,您可以编写以下内容:
template<typename T>
concept has_toString = requires(const T& t) {
t.toString();
};
template<typename T>
std::string optionalToString(const T& obj)
{
if constexpr (has_toString<T>)
return obj.toString();
else
return "toString not defined";
}
Run Code Online (Sandbox Code Playgroud)
小智 6
如果方法碰巧在基类中定义,则litb提供的标准C++解决方案将无法按预期工作.
有关处理此情况的解决方案,请参阅:
俄语:http: //www.rsdn.ru/forum/message/2759773.1.aspx
Roman.Perepelitsa的英文翻译:http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
这非常聪明.但是,这个解决方案的一个问题是,如果被测试的类型是不能用作基类的类型(例如原始类型),则会产生编译器错误
在Visual Studio中,我注意到如果使用没有参数的方法,则需要在argments周围插入一对额外的redundant()以在sizeof表达式中推导出().
我在另一个线程中写了一个答案(与上面的解决方案不同)也检查继承的成员函数:
以下是该解决方案的一些示例:
我们正在检查具有以下签名的成员:
T::const_iterator begin() const
template<class T> struct has_const_begin
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U const * data,
typename std::enable_if<std::is_same<
typename U::const_iterator,
decltype(data->begin())
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};
Run Code Online (Sandbox Code Playgroud)
请注意,它甚至检查方法的常量,并使用原始类型.(我的意思has_const_begin<int>::value
是假,不会导致编译时错误.)
现在我们正在寻找签名: void foo(MyClass&, unsigned)
template<class T> struct has_foo
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U * data, MyClass* arg1 = 0,
typename std::enable_if<std::is_void<
decltype(data->foo(*arg1, 1u))
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};
Run Code Online (Sandbox Code Playgroud)
请注意,MyClass不必是默认构造或满足任何特殊概念.该技术也适用于模板成员.
我急切地等待着这个意见.
通过编写Has_foo
概念检查使用 SFINAE 和模板部分特化的示例:
#include <type_traits>
struct A{};
struct B{ int foo(int a, int b);};
struct C{void foo(int a, int b);};
struct D{int foo();};
struct E: public B{};
// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;
template<typename T, typename = void> struct Has_foo: std::false_type{};
template<typename T>
struct Has_foo<T, void_t<
std::enable_if_t<
std::is_same<
int,
decltype(std::declval<T>().foo((int)0, (int)0))
>::value
>
>>: std::true_type{};
static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");
Run Code Online (Sandbox Code Playgroud)
在 C++17 中实现它的另一种方法(受 boost:hana 启发)。
一次写,多次使用。它不需要has_something<T>
SFINAE 类型特征类。
////////////////////////////////////////////
// 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
////////////////////////////////////////////
#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";
}
Run Code Online (Sandbox Code Playgroud)
我修改了/sf/answers/18486191/ 中提供的解决方案,使其更加通用。此外,由于它不使用任何新的 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); \
}
Run Code Online (Sandbox Code Playgroud)
以下宏可用于检查特定类是否具有特定成员函数,并带有任何给定数量的参数。
/**
* @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); \
}
Run Code Online (Sandbox Code Playgroud)
我们可以使用上述 2 个宏来执行对 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;
}
Run Code Online (Sandbox Code Playgroud)
我知道这个问题已经有很多年了,但我认为对于像我这样的人来说,拥有一个更完整的更新答案会很有用,该答案也适用于const
重载方法,例如std::vector<>::begin
.
根据该答案和我的后续问题的答案,这是一个更完整的答案。请注意,这仅适用于 C++11 及更高版本。
#include <iostream>
#include <vector>
class EmptyClass{};
template <typename T>
class has_begin
{
private:
has_begin() = delete;
struct one { char x[1]; };
struct two { char x[2]; };
template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
template <typename C> static two test(...);
public:
static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
int main(int argc, char *argv[])
{
std::cout << std::boolalpha;
std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
或者更短的版本:
#include <iostream>
#include <vector>
class EmptyClass{};
template <typename T, typename = void>
struct has_begin : std::false_type {};
template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};
int main(int argc, char *argv[])
{
std::cout << std::boolalpha;
std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
请注意,此处必须提供完整的示例调用。这意味着,如果我们测试该resize
方法是否存在,那么我们会将resize(0)
.
深层魔法解释:
该问题的第一个答案使用了test( decltype(&C::helloworld) )
;然而,当正在测试的方法由于 const 重载而不明确时,就会出现问题,从而导致替换尝试失败。
为了解决这个歧义,我们使用一个 void 语句,它可以接受任何参数,因为它总是被转换为 a noop
,因此歧义被消除,并且只要该方法存在,调用就有效:
has_begin<T, decltype(void(std::declval<T &>().begin()))>
Run Code Online (Sandbox Code Playgroud)
以下是按顺序发生的事情:我们用来std::declval<T &>()
创建一个可调用值,begin
然后可以调用该值。之后, 的值begin
作为参数传递给 void 语句。然后,我们使用内置函数检索该 void 表达式的类型decltype
,以便它可以用作模板类型参数。如果begin
不存在,则替换无效,并且根据 SFINAE,将使用其他声明。
归档时间: |
|
查看次数: |
136680 次 |
最近记录: |