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

时间:2017-12-23 16:14:01

标签: c++ c++11 templates sfinae enable-if

我正在尝试递归地应用type_trait has_fun,以便C仅在funT成员函数时启用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

预期产出:

{{1}}

4 个答案:

答案 0 :(得分:1)

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>> >为假。

为简洁起见,此答案使用,但这些内容可以追溯到(如void_t)。

答案 1 :(得分:1)

您需要允许编译器在方法上使用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

答案 2 :(得分:1)

首先,我建议您对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;

你知道你得到零,因为fun()中的C<A>函数是模板一。

不仅如此:如果fun()中的T函数不是void函数,那么该行

  t.fun();
<{1>}函数中的

会导致错误。

建议:如果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()

答案 3 :(得分:0)

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

SimpleSelect

如果大多数C实际上都是在这两种情况下共享的,那么你也可以将该共享部分转移到基础中。