是否可以编写一个模板来根据类上是否定义了某个成员函数来改变行为?
这是我想写的一个简单的例子:
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”部分。
是的,使用 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 对其进行了测试。我不知道它是否可以移植到运行不同编译器的其他平台。
这个问题很老了,但是在 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);
}
现在进行一些解释。首先,如果 decltype
中的第一个表达式无效(也就是该函数不存在),我使用 expression SFINAE 将 serialize(_imp)
函数排除在重载决议之外。
void()
用于使所有这些函数的返回类型为 void
。
如果两者都可用,则 0
参数用于首选 os << obj
重载(文字 0
属于 int
类型,因此第一个重载是更好的匹配)。
现在,您可能需要一个 trait 来检查一个函数是否存在。幸运的是,这很容易写。但是请注意,您需要自己为可能需要的每个不同的函数名称编写一个特征。
#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)){};
并继续进行解释。首先,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;
}
出于同样的原因,int
和 long
参数仍然存在。数组指针用于提供可以使用 sizeof
的上下文。
decltype
优于 sizeof
的优点还在于,临时函数不会由函数调用的特制规则引入(因此您不必具有对返回类型的析构函数的访问权限,也不会导致隐式如果返回类型是类模板实例化,则实例化)。
static_assert(has_stream<X, char>() == true, "fail X");
将编译而不是断言,因为 char 可转换为 int,所以如果不需要这种行为并且希望所有参数类型都匹配,我不知道如何实现?
sfinae_false
对应项,并在 long
覆盖上使用了一个返回类型,该类型检测到存在析构函数。这排除了仍然不完整或没有公共析构函数的类型。排除非公共析构函数对我来说是可以接受的。
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); \
}
上面的模板和宏试图实例化一个模板,给它一个成员函数指针类型,以及实际的成员函数指针。如果类型不适合,SFINAE 会导致模板被忽略。像这样的用法:
HAS_MEM_FUNC(toString, has_to_string);
template<typename T> void
doSomething() {
if(has_to_string<T, std::string(T::*)()>::value) {
...
} else {
...
}
}
但请注意,您不能只在该 if
分支中调用该 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.";
}
玩得开心。它的优点是它也适用于重载的成员函数,也适用于 const
成员函数(记住使用 std::string(T::*)() const
作为成员函数指针类型!)。
type_check
来确保签名完全一致。有没有办法让它匹配任何可以调用的方法,就像调用带有签名 Sign
的方法一样? (例如,如果 Sign
= std::string(T::*)()
,则允许 std::string T::toString(int default = 42, ...)
匹配。)
T
不能是原始类型,因为指向 T 方法的指针声明不受 SFINAE 约束,并且对于任何非 T 类都会出错。IMO 最简单的解决方案是结合使用提升的 is_class
检查。
toString
是模板化函数,我该如何进行这项工作?
C++20 - 需要表达式
C++20 附带了一些概念和各种工具,例如 requires
expressions,它们是检查函数是否存在的内置方法。使用它们,您可以按如下方式重写您的 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";
}
Pre-C++20 - 检测工具包
N4502 提出了一个包含在 C++17 标准库中的检测工具包,最终将其纳入库基础 TS v2。它很可能永远不会进入标准,因为它已被 requires
表达式所包含,但它仍然以某种优雅的方式解决了问题。该工具包引入了一些元函数,包括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>;
请注意,上面的示例未经测试。检测工具包在标准库中尚不可用,但该提案包含一个完整的实现,如果您真的需要它,您可以轻松复制它。它与 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";
}
C++14 - Boost.Hana
Boost.Hana 显然建立在这个特定示例的基础上,并在其文档中提供了 C++14 的解决方案,所以我将直接引用它:
[...] Hana 提供了一个 is_valid 函数,该函数可以与 C++14 通用 lambdas 组合以获得相同事物的更简洁的实现:auto has_toString = hana::is_valid([](auto&& obj) -> decltype( obj.toString()) { });这给我们留下了一个函数对象 has_toString,它返回给定表达式在我们传递给它的参数上是否有效。结果作为 IntegralConstant 返回,因此 constexpr-ness 在这里不是问题,因为函数的结果无论如何都表示为类型。现在,除了不那么冗长(这是一条线!)之外,意图更加清晰。其他好处是 has_toString 可以传递给更高阶的算法,也可以在函数范围内定义,因此不需要用实现细节污染命名空间范围。
Boost.TTI
执行这种检查的另一个有点惯用的工具包 - 尽管不太优雅 - 是 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<T, std::string>::value
替换为 has_member_function_toString<std::string T::* ()>::value
。
虽然这个问题已经两年了,但我敢于添加我的答案。希望它能澄清以前的、无可争议的优秀解决方案。我采纳了 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));
};
我用 gcc 4.1.2 检查了它。功劳主要归功于 Nicola Bonelli 和 Johannes Schaub,所以如果我的回答对您有帮助,请给他们投票:)
toString
的类提供单独的模板特化。如果您编写一个通用库,希望与任何类一起工作(想想像 boost 之类的东西),那么要求用户定义一些晦涩模板的额外特化可能是不可接受的。有时最好编写一个非常复杂的代码来保持公共接口尽可能简单。
ToString
类模板中嵌套函数指针类型的原因。 IIUC,这是为了避免 Type
不是类时的实例化错误,对吧?
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";
}
template<typename>
,我就无法完成这项工作:它没有被考虑解决。
好吧,这个问题已经有一长串答案了,但我想强调一下 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 {};
然后是变量部分,您可以在其中指定要查找的内容(类型、成员类型、函数、成员函数等)。在 OP 的情况下:
template <typename T>
using toString_t = decltype(std::declval<T>().toString());
template <typename T>
using has_toString = detect<T, toString_t>;
以下示例取自 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>;
与上面描述的其他实现相比,这个实现相当简单:一组减少的工具(void_t
和 detect
)就足够了,不需要毛茸茸的宏。此外,据报道(参见 N4502)它比以前的方法更有效(编译时和编译器内存消耗)。
这是一个live example。它适用于 Clang,但不幸的是,5.1 之前的 GCC 版本遵循对 C++11 标准的不同解释,导致 void_t
无法按预期工作。 Yakk 已经提供了解决方法:使用 void_t
(void_t in parameter list works but not as return type) 的以下定义:
#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
这就是类型特征的用途。不幸的是,它们必须手动定义。在您的情况下,想象以下情况:
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;
}
&T::x
显式或通过将其绑定到引用来隐式)。
这是针对一般问题的 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 {};
特征 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++ 特性。这样做可能不值得,因为(内联代码的好处)不值得付出代价(几乎没有人了解它是如何工作的),但上述解决方案的存在可能会引起人们的兴趣。
has_to_string
并不重要。
使用 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";
}
以下是一些用法片段: *所有这些的胆量都在更远的地方
检查给定类中的成员 x
。可以是 var、func、class、union 或 enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
检查成员函数 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;
检查成员变量 x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
检查成员类 x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
检查会员工会x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
检查成员枚举 x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
无论签名如何,检查任何成员函数 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;
或者
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;
细节和核心:
/*
- 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;
};
宏(暗黑破坏神!):
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 \
; \
}
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 {}
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 {}
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 {}
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 {}
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 {}
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 \
; \
}
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)
sig_check<func_sig, &T::func_name>
更改为自由函数检查:sig_check<func_sig, &func_name>
它无法使用提及我们要检查的函数名称的“未声明标识符”构建?因为我希望 SFINAE 不会出错,它只是为会员做的,为什么不为免费功能呢?
在 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";
}
has_member
宏。如果您在类中使用它来检查私有成员的存在,那么是的,它确实有效。如果您在类之外使用该宏,例如在某些函数中,则宏不起作用。但是,如果您将此函数添加到 friend
列表中,则它可以工作。
我在另一个线程中写了一个答案(与上面的解决方案不同)也检查继承的成员函数:
SFINAE to check for inherited member functions
以下是该解决方案的一些示例:
示例 1:
我们正在检查具有以下签名的成员: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));
};
请注意,它甚至会检查方法的常量性,并且也适用于原始类型。 (我的意思是 has_const_begin<int>::value
是假的,不会导致编译时错误。)
示例 2
现在我们正在寻找签名: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));
};
请注意 MyClass 不必是默认可构造的或满足任何特殊概念。该技术也适用于模板成员。
我急切地等待对此的意见。
这是我在 C++20 中找到的最简洁的方式,与您的问题非常接近:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (requires { obj->toString(); })
return obj->toString();
else
return "toString not defined";
}
在 Godbolt 上实时观看:https://gcc.godbolt.org/z/5jb1d93Ms
如果该方法恰好在基类中定义,则 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 中,我注意到如果使用没有参数的方法,则需要在参数周围插入一对多余的 () 以在 sizeof 表达式中推断 ()。
struct g { void f(); private: void f(int); };
中检查“f”函数,因为其中一个函数是私有的(这是因为代码执行 using g::f;
,如果任何 f
不可访问,则它会失败)。
现在这是一个很好的小谜题 - 很好的问题!
这是不依赖非标准 typeof
运算符的 Nicola Bonelli's solution 的替代方法。
不幸的是,它不适用于 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 考虑 T
的 toString()
成员函数在实例化模板时在 doToString()
中的调用站点执行名称查找时,这是不正确的(即使它实际上是我的行为在这种情况下寻找)。
GCC 和 Digital Mars 的行为看起来是正确的 - 在这两种情况下,非成员 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;
}
使用 SFINAE 和模板偏特化的示例,通过编写 Has_foo
概念检查:
#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");
typename
默认为 int
,那么我们可以执行 Has_foo<T, decltype(std::declval<T>().foo(0, 0))> : std::true_type {};
(godbolt)
我修改了 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); \
}
我们可以使用上面的 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;
}
HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... );
... template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
奇怪的没有人提出我曾经在这个网站上看到的以下好技巧:
template <class T>
struct has_foo
{
struct S { void foo(...); };
struct derived : S, T {};
template <typename V, V> struct W {};
template <typename X>
char (&test(W<void (X::*)(), &X::foo> *))[1];
template <typename>
char (&test(...))[2];
static const bool value = sizeof(test<derived>(0)) == 1;
};
你必须确保 T 是一个类。似乎 foo 查找中的歧义是替换失败。我让它在 gcc 上工作,但不确定它是否是标准的。
可用于检查类型是否支持某些“功能”的通用模板:
#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;
};
检查是否存在与签名 double(const char*)
兼容的方法 foo
的模板
// 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
has_foo
内联到 is_supported
的模板调用中。我想要调用类似:std::cout << is_supported<magic.foo(), struct1>::value << std::endl;
。原因是,我想在检查函数之前为要检查的每个不同的函数签名定义一个 has_foo
?
我知道这个问题已经存在多年了,但我认为对于像我这样的人来说,拥有一个更完整的更新答案会很有用,该答案也适用于 const
重载方法,例如 std::vector<>::begin
。
根据我的后续问题中的那个 answer 和那个 answer,这里有一个更完整的答案。请注意,这只适用于 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;
}
或更短的版本:
#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;
}
请注意,此处必须提供完整的示例调用。这意味着如果我们测试 resize
方法的存在,那么我们将放置 resize(0)
。
深魔法解释:
该问题发布的第一个答案使用 test( decltype(&C::helloworld) )
;但是,当它正在测试的方法由于 const 重载而模棱两可时,这是有问题的,从而使替换尝试失败。
为了解决这种歧义,我们使用 void 语句,它可以接受任何参数,因为它总是被转换为 noop
,因此歧义被取消,只要方法存在,调用就有效:
has_begin<T, decltype(void(std::declval<T &>().begin()))>
以下是按顺序发生的事情:我们使用 std::declval<T &>()
创建一个可调用值,然后可以调用 begin
。之后 begin
的值作为参数传递给 void 语句。然后我们使用内置的 decltype
检索该 void 表达式的类型,以便它可以用作模板类型参数。如果 begin
不存在,则替换无效,并且根据 SFINAE 使用其他声明代替。
void(...)
部分。我原以为这会起作用:template <auto> using v_to_void = void;
... v_to_void<std::declval<T&>().begin()>
(避免 decltype
)。知道为什么不这样做吗?
这个解决方案怎么样?
#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 { };
toString
过载,则失败,因为 &U::toString
不明确。
这里有很多答案,但我找不到一个版本,它执行真正的方法解析排序,同时不使用任何较新的 c++ 功能(仅使用 c++98 功能)。注意:此版本经过测试,可与 vc++2013、g++ 5.2.0 和在线编译器一起使用。
所以我想出了一个只使用 sizeof() 的版本:
template<typename T> T declval(void);
struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);
struct yes { char v[1]; };
struct no { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};
template<typename T>
struct has_awesome_member {
template<typename U> static yes_no<(sizeof((
declval<U>().awesome_member(),fake_void()
))!=0)> check(int);
template<typename> static no check(...);
enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};
struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };
static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");
现场演示(带有扩展的返回类型检查和 vc++2010 解决方法):http://cpp.sh/5b2vs
没有来源,因为我自己想出了它。
在 g++ 编译器上运行 Live demo 时,请注意允许数组大小为 0,这意味着使用的 static_assert 不会触发编译器错误,即使它失败。一种常用的解决方法是将宏中的“typedef”替换为“extern”。
static_assert(false);
)。我将它与 CRTP 结合使用,我想确定派生类是否具有特定的功能——结果证明它不起作用,但你的断言总是通过。我为那个人掉了一些头发。
operator ,
,因为这是由内置 void 类型实现的唯一运算符(您可以编写 ((void)0, foo())
,但像 ((void)0 + foo())
中的任何其他运算符总是会导致编译器错误并且不能被覆盖),这意味着这是能够检测具有 void 返回类型的函数所必需的。 - 至于命名空间污染:当然,您可以将所有内容(除了 operator ,()
,它必须作为全局运算符保持可见)放入某个命名空间并调整 has_awesome_member
以简单地使用该命名空间。
我的看法:在不为每一个都制作冗长的类型特征,或使用实验性功能或长代码的情况下,普遍确定某些东西是否可调用:
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);
这是我的版本,它处理所有可能的具有任意数量的成员函数重载,包括模板成员函数,可能带有默认参数。当使用给定的 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 扩展(例如 gcc)的非 c++11。您可以用自己的宏替换 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
您可以跳过 C++14 中的所有元编程,只需使用 Fit 库中的 fit::conditional
编写:
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"; }
);
但是,如果您使用的编译器不支持通用 lambda,则必须编写单独的函数对象:
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()
);
fit
或标准以外的任何库,编写它有多容易?
可能不如其他示例好,但这是我为 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');
}
这是工作代码的示例。
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
调用时采用 long
的函数。
如果实现了函数,您可以对返回 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
}
我有一个类似的问题:
一个模板类,它可能派生自几个基类,一些具有特定成员,而另一些则没有。
我以类似于“typeof”(Nicola Bonelli 的)答案的方式解决了它,但使用 decltype,因此它可以在 MSVS 上正确编译和运行:
#include <iostream>
#include <string>
struct Generic {};
struct HasMember
{
HasMember() : _a(1) {};
int _a;
};
// SFINAE test
template <typename T>
class S : public T
{
public:
std::string foo (std::string b)
{
return foo2<T>(b,0);
}
protected:
template <typename T> std::string foo2 (std::string b, decltype (T::_a))
{
return b + std::to_string(T::_a);
}
template <typename T> std::string foo2 (std::string b, ...)
{
return b + "No";
}
};
int main(int argc, char *argv[])
{
S<HasMember> d1;
S<Generic> d2;
std::cout << d1.foo("HasMember: ") << std::endl;
std::cout << d2.foo("Generic: ") << std::endl;
return 0;
}
不定期副业成功案例分享
typeof
替换为decltype
,例如,通过 -std=c++0x。