我发现类似的问题和答案像这一个 。 然而,正如我尝试了,这SFINAE测试仅当测试构件是在类被测试直接定义成功。 例如,下面的,类B
, D1
打印HAS
,而其他两个打印NOT HAS
。 有没有一种方法来确定,如果一个类有一个成员,无论是由自己定义,或基类和基类的名称没有在这种情况下,已知的。 的动机是,我想编写一个通用的函数,将调用某个方法,如果它存在(从基地与否,参数的类型是通用的,沿着它的可能基地的类型离开)。
#include <iostream>
class HasFoo
{
public :
typedef char Small;
typedef struct {char; char;} Large;
template <typename C, void (C::*) ()> class SFINAE {};
template <typename C> static Small test (SFINAE<C, &C::foo> *)
{
std::cout << "HAS" << std::endl;
}
template <typename C> static Large test (...)
{
std::cout << "NOT HAS" << std::endl;
}
};
class B
{
public :
void foo () {}
};
class D1 : public B
{
public :
void foo () {} // overide
};
class D2 : public B
{
public :
using B::foo;
};
class D3 : public B {};
int main ()
{
HasFoo::test<B>(0);
HasFoo::test<D1>(0);
HasFoo::test<D2>(0);
HasFoo::test<D3>(0);
}
不幸的是它不会在C ++ 03至少是可能的,我怀疑在C ++ 11也。
几点非常重要:
- 所提出的SFINAE只能如果方法是
public
- 即使SFINAE会工作为基础的方法,点(1)适用; 因为
private
和protected
继承SFINAE最终可能没用 - 假设你可能想要仅处理
public
方法/继承,代码HasFoo::test<>
可以增强用于取其中基类还可以被传递的多个参数; std::is_base_of<>
可用于基/派生关系的进一步验证; 然后将相同的逻辑为基类还
在C ++ 03,这是不幸的是不可能的,对不起。
在C ++ 11,事情就变得容易得多感谢神奇的decltype
。 decltype
让你写表达式来推断其结果的类型,这样你就可以完美地命名基类中的一员。 如果该方法的模板,然后SFINAE适用于decltype
表达。
#include <iostream>
template <typename T>
auto has_foo(T& t) -> decltype(t.foo(), bool()) { return true; }
bool has_foo(...) { return false; }
struct Base {
void foo() {}
};
struct Derived1: Base {
void foo() {}
};
struct Derived2: Base {
using Base::foo;
};
struct Derived3: Base {
};
int main() {
Base b; Derived1 d1; Derived2 d2; Derived3 d3;
std::cout << has_foo(b) << " "
<< has_foo(d1) << " "
<< has_foo(d2) << " "
<< has_foo(d3) << "\n";
}
不幸的是ideone有一个版本的GCC是太老了,这和铿锵3.0是再好不过的。
有一种方法来确定是否一个类层次结构有一个给定名称的成员。 它采用SFINAE,并通过创建一个模棱两可介绍substituation失败的名称查找。 此外,有一种方法来测试,如果公众成员可赎回; 然而,没有一种方法来确定是否一个成员是公众SFINAE。
这里是一个例子:
#include <iostream>
template < typename T >
struct has_foo
{
typedef char yes;
typedef char no[2];
// Type that has a member with the name that will be checked.
struct fallback { int foo; };
// Type that will inherit from both T and mixin to guarantee that mixed_type
// has the desired member. If T::foo exists, then &mixed_type::foo will be
// ambiguous. Otherwise, if T::foo does not exists, then &mixed_type::foo
// will successfully resolve to fallback::foo.
struct mixed_type: T, fallback {};
template < typename U, U > struct type_check {};
// If substituation does not fail, then &U::foo is not ambiguous, indicating
// that mixed_type only has one member named foo (i.e. fallback::foo).
template < typename U > static no& test( type_check< int (fallback::*),
&U::foo >* = 0 );
// Substituation failed, so &U::foo is ambiguous, indicating that mixed_type
// has multiple members named foo. Thus, T::foo exists.
template < typename U > static yes& test( ... );
static const bool value = sizeof( yes ) ==
sizeof( test< mixed_type >( NULL ) );
};
namespace detail {
class yes {};
class no{ yes m[2]; };
// sizeof will be used to determine what function is selected given an
// expression. An overloaded comma operator will be used to branch based
// on types at compile-time.
// With ( helper, anything-other-than-no, yes ) return yes.
// With ( helper, no, yes ) return no.
struct helper {};
// Return helper.
template < typename T > helper operator,( helper, const T& );
// Overloads.
yes operator,( helper, yes ); // For ( helper, yes ) return yes.
no operator,( helper, no ); // For ( helper, no ) return no.
no operator,( no, yes ); // For ( no, yes ) return no.
} // namespace detail
template < typename T >
struct can_call_foo
{
struct fallback { ::detail::no foo( ... ) const; };
// Type that will inherit from T and fallback, this guarantees
// that mixed_type has a foo method.
struct mixed_type: T, fallback
{
using T::foo;
using fallback::foo;
};
// U has a foo member.
template < typename U, bool = has_foo< U >::value >
struct impl
{
// Create the type sequence.
// - Start with helper to guarantee the custom comma operator is used.
// - This is evaluationg the expression, not executing, so cast null
// to a mixed_type pointer, then invoke foo. If T::foo is selected,
// then the comma operator returns helper. Otherwise, fooback::foo
// is selected, and the comma operator returns no.
// - Either helper or no was returned from the first comma operator
// evaluation. If ( helper, yes ) remains, then yes will be returned.
// Otherwise, ( no, yes ) remains; thus no will be returned.
static const bool value = sizeof( ::detail::yes ) ==
sizeof( ::detail::helper(),
((mixed_type*)0)->foo(),
::detail::yes() );
};
// U does not have a 'foo' member.
template < typename U >
struct impl< U, false >
{
static const bool value = false;
};
static const bool value = impl< T >::value;
};
// Types containing a foo member function.
struct B { void foo(); };
struct D1: B { bool foo(); }; // hide B::foo
struct D2: B { using B::foo; }; // no-op, as no hiding occured.
struct D3: B { };
// Type that do not have a member foo function.
struct F {};
// Type that has foo but it is not callable via T::foo().
struct G { int foo; };
struct G1 { bool foo( int ); };
int main ()
{
std::cout << "B: " << has_foo< B >::value << " - "
<< can_call_foo< B >::value << "\n"
<< "D1: " << has_foo< D1 >::value << " - "
<< can_call_foo< D1 >::value << "\n"
<< "D2: " << has_foo< D2 >::value << " - "
<< can_call_foo< D2 >::value << "\n"
<< "D3: " << has_foo< D3 >::value << " - "
<< can_call_foo< D3 >::value << "\n"
<< "F: " << has_foo< F >::value << " - "
<< can_call_foo< F >::value << "\n"
<< "G: " << has_foo< G >::value << " - "
<< can_call_foo< G >::value << "\n"
<< "G1: " << has_foo< G1 >::value << " - "
<< can_call_foo< G1 >::value << "\n"
<< std::endl;
return 0;
}
这将产生以下的输出:
B: 1 - 1
D1: 1 - 1
D2: 1 - 1
D3: 1 - 1
F: 0 - 0
G: 1 - 0
G1: 1 - 0
has_foo
仅检查成员命名的存在foo
。 它不验证foo
是一个可调用的成员(公共成员函数或公共成员是一个仿函数)。
can_call_foo
检查是否T::foo()
是可调用。 如果T::foo()
是不公开的,那么就会出现一个编译器错误。 据我所知,目前还没有办法阻止这种通过SFINAE。 对于一个更完整,更辉煌,但相当复杂的解决方案,请点击这里 。
文章来源: Is it possible to check if a member function is defined for a class even if the member is inherited from an unknown base class