检查是否存在(重载的)成员函数
Checking for existence of an (overloaded) member function
有很多关于检查成员函数是否存在的回答问题:例如,
Is it possible to write a template to check for a function's existence?
但如果函数重载,此方法将失败。这是对该问题的最高评价答案稍作修改的代码。
#include <iostream>
#include <vector>
struct Hello
{
int helloworld(int x) { return 0; }
int helloworld(std::vector<int> x) { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
typedef long two;
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;
}
这段代码打印出来:
0
0
但是:
1
0
如果第二个helloworld()
被注释掉。
所以我的问题是是否可以检查成员函数是否存在,而不管它是否重载。
// use std::void_t in C++... 17 I think? ... instead of this:
template<class...>struct void_type { using type = void; };
template<class...Ts>using void_t = typename void_type<Ts...>::type;
template<class T, class...Args>
using hello_world_ify = decltype(
std::declval<T>().helloworld( std::declval<Args>()... )
);
template<class T, class Sig, class=void>
struct has_helloworld:std::false_type{};
template<class T, class...Args>
struct has_helloworld<T, void(Args...),
void_t<
hello_world_ify<T, Args...>
>
>:std::true_type{};
template<class T, class R, class...Args>
struct has_helloworld<T, R(Args...),
typename std::enable_if<
!std::is_same<R,void>::value &&
std::is_convertible<
hello_world_ify<T, Args...>,
R
>::value
>::type
>:std::true_type{};
我将上面的内容放在 details
命名空间中,然后公开一个 template<class T, class Sig> struct has_helloworld:details::has_helloworld<T,Sig>{};
这样就不会有人传递类型来代替默认的 void
。
我们使用 SFINAE 来检测我们是否可以调用 T.helloworld(Args...)
。如果传入的签名是void(blah)
,我们只是检测是否可以调用——如果不能,我们测试T.helloworld(Args...)
的return类型是否可以转换为return ] 签名类型。
MSVC 在使用 decltype
执行 SFINAE 时存在重大问题,因此上述内容在 MSVC 中可能不起作用。
请注意,has_helloworld<T, R(Args...)>
对应于传入右值 T
,在该右值上下文中调用 helloworld
并传递右值 Args...
。要使值成为左值,请添加 &
。要使它们成为常量左值,请在类型上使用 const&
。但是,这应该主要只在某些极端情况下才重要。
对于更一般的情况,如果没有匹配的样本签名,就无法检测到重写方法的存在。
上面的内容可以用来处理精确的签名匹配。
有趣的是,如果存在签名冲突(即在调用的直接上下文中会发生错误),它就好像没有这样的方法一样。
由于它依赖于 SFINAE,因此非即时上下文中的错误不会触发失败。
在 C++ 中,[到目前为止] 无法获取重载集的地址:当您获取函数或成员函数的地址时,该函数要么是唯一的,要么必须选择适当的指针,例如,通过立即将指针传递给合适的函数或对其进行强制转换。换句话说,如果 helloworld
不唯一,则表达式 &C::helloworld
失败。据我所知,结果是无法确定可能重载的名称是作为 class 成员还是作为普通函数存在。
但是,通常您需要对名称进行一些处理。也就是说,如果知道函数是否存在 和 是否可以用一组指定类型的参数调用就足够了,问题就变得很不一样了:这个问题可以通过以下方式回答尝试相应的调用并在支持 SFINAE 的上下文中确定其类型,例如:
template <typename T, typename... Args>
class has_helloworld
{
template <typename C,
typename = decltype( std::declval<C>().helloworld(std::declval<Args>()...) )>
static std::true_type test(int);
template <typename C>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
然后您将使用此类型来确定是否存在可以适当调用的成员,例如:
std::cout << std::boolalpha
<< has_helloworld<Hello>::value << '\n' // false
<< has_helloworld<Hello, int>::value << '\n' // true
<< has_helloworld<Generic>::value << '\n'; // false
正如其他答案所说,一般来说,这是不可能的。
但是,如果您可以“使”class 编写器始终从预定义的基础 class 继承,并且您检查该名称的“某物”是否存在而不是“成员函数”是否存在那个名字,也许你可以这样做:
struct B
{
static char const helloworld = 0;
};
template <typename D>
std::true_type test_helloworld(...);
template <typename D, typename = std::enable_if_t<&D::helloworld == &B::helloworld>>
std::false_type test_helloworld(int);
template <typename D, typename = std::enable_if_t<std::is_base_of_v<B, D>>>
using has_helloworld = decltype(test_helloworld<D>(0));
struct D1 : B
{
};
struct D2 : B
{
void helloworld() {}
};
struct D3 : B
{
void helloworld() {}
void helloworld(int) {}
};
struct D4 : B
{
using helloworld = int;
};
struct D5 //no `: B`
{};
int main()
{
static_assert(has_helloworld<D1>::value == false);
static_assert(has_helloworld<D2>::value == true);
static_assert(has_helloworld<D3>::value == true);
static_assert(has_helloworld<D4>::value == true);
//auto v = has_helloworld<D5>::value;//This fails the compile.
return 0;
}
有很多关于检查成员函数是否存在的回答问题:例如, Is it possible to write a template to check for a function's existence?
但如果函数重载,此方法将失败。这是对该问题的最高评价答案稍作修改的代码。
#include <iostream>
#include <vector>
struct Hello
{
int helloworld(int x) { return 0; }
int helloworld(std::vector<int> x) { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
typedef long two;
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;
}
这段代码打印出来:
0
0
但是:
1
0
如果第二个helloworld()
被注释掉。
所以我的问题是是否可以检查成员函数是否存在,而不管它是否重载。
// use std::void_t in C++... 17 I think? ... instead of this:
template<class...>struct void_type { using type = void; };
template<class...Ts>using void_t = typename void_type<Ts...>::type;
template<class T, class...Args>
using hello_world_ify = decltype(
std::declval<T>().helloworld( std::declval<Args>()... )
);
template<class T, class Sig, class=void>
struct has_helloworld:std::false_type{};
template<class T, class...Args>
struct has_helloworld<T, void(Args...),
void_t<
hello_world_ify<T, Args...>
>
>:std::true_type{};
template<class T, class R, class...Args>
struct has_helloworld<T, R(Args...),
typename std::enable_if<
!std::is_same<R,void>::value &&
std::is_convertible<
hello_world_ify<T, Args...>,
R
>::value
>::type
>:std::true_type{};
我将上面的内容放在 details
命名空间中,然后公开一个 template<class T, class Sig> struct has_helloworld:details::has_helloworld<T,Sig>{};
这样就不会有人传递类型来代替默认的 void
。
我们使用 SFINAE 来检测我们是否可以调用 T.helloworld(Args...)
。如果传入的签名是void(blah)
,我们只是检测是否可以调用——如果不能,我们测试T.helloworld(Args...)
的return类型是否可以转换为return ] 签名类型。
MSVC 在使用 decltype
执行 SFINAE 时存在重大问题,因此上述内容在 MSVC 中可能不起作用。
请注意,has_helloworld<T, R(Args...)>
对应于传入右值 T
,在该右值上下文中调用 helloworld
并传递右值 Args...
。要使值成为左值,请添加 &
。要使它们成为常量左值,请在类型上使用 const&
。但是,这应该主要只在某些极端情况下才重要。
对于更一般的情况,如果没有匹配的样本签名,就无法检测到重写方法的存在。
上面的内容可以用来处理精确的签名匹配。
有趣的是,如果存在签名冲突(即在调用的直接上下文中会发生错误),它就好像没有这样的方法一样。
由于它依赖于 SFINAE,因此非即时上下文中的错误不会触发失败。
在 C++ 中,[到目前为止] 无法获取重载集的地址:当您获取函数或成员函数的地址时,该函数要么是唯一的,要么必须选择适当的指针,例如,通过立即将指针传递给合适的函数或对其进行强制转换。换句话说,如果 helloworld
不唯一,则表达式 &C::helloworld
失败。据我所知,结果是无法确定可能重载的名称是作为 class 成员还是作为普通函数存在。
但是,通常您需要对名称进行一些处理。也就是说,如果知道函数是否存在 和 是否可以用一组指定类型的参数调用就足够了,问题就变得很不一样了:这个问题可以通过以下方式回答尝试相应的调用并在支持 SFINAE 的上下文中确定其类型,例如:
template <typename T, typename... Args>
class has_helloworld
{
template <typename C,
typename = decltype( std::declval<C>().helloworld(std::declval<Args>()...) )>
static std::true_type test(int);
template <typename C>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
然后您将使用此类型来确定是否存在可以适当调用的成员,例如:
std::cout << std::boolalpha
<< has_helloworld<Hello>::value << '\n' // false
<< has_helloworld<Hello, int>::value << '\n' // true
<< has_helloworld<Generic>::value << '\n'; // false
正如其他答案所说,一般来说,这是不可能的。
但是,如果您可以“使”class 编写器始终从预定义的基础 class 继承,并且您检查该名称的“某物”是否存在而不是“成员函数”是否存在那个名字,也许你可以这样做:
struct B
{
static char const helloworld = 0;
};
template <typename D>
std::true_type test_helloworld(...);
template <typename D, typename = std::enable_if_t<&D::helloworld == &B::helloworld>>
std::false_type test_helloworld(int);
template <typename D, typename = std::enable_if_t<std::is_base_of_v<B, D>>>
using has_helloworld = decltype(test_helloworld<D>(0));
struct D1 : B
{
};
struct D2 : B
{
void helloworld() {}
};
struct D3 : B
{
void helloworld() {}
void helloworld(int) {}
};
struct D4 : B
{
using helloworld = int;
};
struct D5 //no `: B`
{};
int main()
{
static_assert(has_helloworld<D1>::value == false);
static_assert(has_helloworld<D2>::value == true);
static_assert(has_helloworld<D3>::value == true);
static_assert(has_helloworld<D4>::value == true);
//auto v = has_helloworld<D5>::value;//This fails the compile.
return 0;
}