用于成员函数检测的递归type_traits

Recursive type_traits for member function detection

本文关键字:type traits 递归 成员 函数 检测 用于      更新时间:2023-10-16

我正在尝试递归应用type_traithas_fun,以便C仅在T有一个成员函数时才启用其fun成员函数。 有没有办法有条件地检测C::fun

template <typename T>
struct has_fun {
template <class, class> class checker;
template <typename U>
static std::true_type test(checker<U, decltype(&U::fun)> *);
template <typename U>
static std::false_type test(...);
static const bool value = std::is_same<std::true_type, decltype(test<T>(nullptr))>::value;
};
struct A {
void fun(){
std::cout << "this is fun!" << std::endl;
}
};
struct B {
void not_fun(){
std::cout << "this is NOT fun!" << std::endl;
}
};
template<typename T>
struct C {
void fun() {
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};
int main(int argc, char const *argv[])
{
std::cout << has_fun<A>::value << std::endl;
std::cout << has_fun<B>::value << std::endl;
std::cout << has_fun<C<A>>::value << std::endl;
std::cout << has_fun<C<B>>::value << std::endl;
}

输出:

1
0
1
1

预期产出:

1
0
1
0

您需要允许编译器在方法上对 SFINAE

。模板中发生的所有检查仅考虑函数的签名,因此不会考虑您使用的static_assert。

解决方案是在签名中添加复选标记。

直觉上你会写

template<typename T>
struct C {
std::enable_if_t<has_fun<T>::value> fun() {
t.fun();
}
T t;
};

但这不会产生你所期望的:编译器会拒绝编译 C,即使你不调用 C.fun();

为什么?

如果编译器可以证明它永远不会工作,则允许编译器评估代码并发出错误。 因为当你声明 C 时,编译器可以证明 foo() 永远不会被允许,所以编译会失败。

若要解决此问题,可以强制方法具有依赖类型,以便编译器无法证明它总是会失败。

这是诀窍

template<typename T>
struct C {
template<typename Q=T, typename = if_has_fun<Q>>
void fun() {
t.fun();
}
T t;
};

编译器无法证明 Q 将永远是 T,我们检查 Q,而不是 T,因此只有在您调用 fun 时才会执行检查。

https://wandbox.org/permlink/X32bwCqQDb288gVl 的完整工作解决方案

注意:我使用了实验性的检测器,但您可以使用检测器。

但是,您需要替换真正的测试,以检查是否可以正确调用该函数。

template <typename U>
static std::true_type test(checker<U, decltype(std::declval<U>().fun())> *);

请参阅 https://wandbox.org/permlink/MpohZzxvZdurMArP

namespace details{
template<template<class...>class,class,class...>
struct can_apply:std::false_type{};
template<template<class...>class Z,class...Ts>
struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
}
template<template<class...>class Z,class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
template<class T, class...Args>
using dot_fun_r=decltype(std::declval<T>().fun(std::declval<Args>()...));
template<class T, class...Args>
using can_dot_fun = can_apply<dot_fun_r, T, Args...>;

can_dot_funhas_fun的光滑版本。

temple<class U=T&,
std::enable_if_t< can_dot_fun<U>{}, bool > =true
void fun() {
static_cast<U>(t).fun();
}

现在C<B>{}.fun()无效,所以can_dot_fun< C<B>> >是假的。

为了简洁起见,这个答案使用 c++17,但这些片段可以写到 c++11 之前(如void_t)。

首先,我向你推荐一个简化版的has_fun型特征

template <typename T>
struct has_fun
{
template <typename U>
static constexpr auto test (int)
-> decltype( &U::fun, std::true_type{} );
template <typename U>
static constexpr std::false_type test (...);
static constexpr bool value = decltype(test<T>(1))::value;
};

这可以检测类型T是否可用,fun(&T::fun)成员,无论它是变量还是函数,无论函数的签名(如果是函数)。

可能很有用,但请考虑在以下情况下不起作用:(1) 有更多重载方法fun()(2) 当fun()是模板方法时。

使用它,你可以,通过示例,写(SFINAE 启用/禁用fun())C容器,如下所示

template <typename T>
struct C
{
template <typename U = T>
auto fun() -> typename std::enable_if<has_fun<U>::value>::type
{
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};

这有效,因为您可以编写

C<A> ca;
ca.fun();

但是,如果您尝试打印has_fun<C<A>>

std::cout << has_fun<C<A>>::value << std::endl;

您会看到得到零,因为C<A>中的fun()函数是模板函数。

不仅:如果T中的fun()函数不是void函数,则行

t.fun();

C::fun()函数中,导致错误。

建议:更改您的has_fun类型特征以检查,模拟带有std::declval()的调用,如果T具有具有精确签名的fun()方法(在您的情况下void(*)(void))

template <typename T>
struct has_fun
{
template <typename U>
static constexpr auto test (int)
-> decltype( std::declval<U>().fun(), std::true_type{} );
template <typename U>
static constexpr std::false_type test (...);
static constexpr bool value = decltype(test<T>(1))::value;
};

现在has_fun<C<A>>::value也是真的,因为也适用于重载和模板函数;现在C::fun()方法是安全的,因为只有当T具有具有正确签名的fun()方法时才会启用。

以下是完整的工作示例

#include <iostream>
#include <type_traits>
template <typename T>
struct has_fun
{
template <typename U>
static constexpr auto test (int)
-> decltype( std::declval<U>().fun(), std::true_type{} );
template <typename U>
static constexpr std::false_type test (...);
static constexpr bool value = decltype(test<T>(1))::value;
};
struct A
{ void fun(){ std::cout << "this is fun!" << std::endl; } };
struct B
{ void not_fun(){ std::cout << "this is NOT fun!" << std::endl; } };
template <typename T>
struct C
{
template <typename U = T>
auto fun() -> typename std::enable_if<has_fun<U>::value>::type
{
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};
int main ()
{
std::cout << has_fun<A>::value << std::endl;
std::cout << has_fun<B>::value << std::endl;
std::cout << has_fun<C<A>>::value << std::endl;
std::cout << has_fun<C<B>>::value << std::endl;
}

这可以通过 C 的两个实现来完成,一个很有趣,另一个没有,还有一个额外的 std::enable_if_t artempte 参数:

template<typename T, std::enable_if_t<has_fun<T>::value> * = nullptr>
struct C
{
void fun()
{ ... }
};
template<typename T, std::enable_if_t<!has_fun<T>::value> * = nullptr>
struct C
{
// no fun()
};

如果 C 的大部分实际上在两个案例之间共享,您也可以将该共享部分拆分为一个基数。