成员函数检测递归type_traits

Recursive type_traits for member function detection

我正在尝试递归地应用 type_trait has_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

这可以通过 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 的大部分实际上在这两种情况之间共享,您也可以将该共享部分分离到一个基础中。

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>> > 是错误的。

此答案使用 for brevity, but the pieces can be written as far back as (如 void_t)。

您需要允许编译器将 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

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