【问题标题】:Recursive type_traits for member function detection用于成员函数检测的递归 type_traits
【发布时间】:2017-12-23 16:14:01
【问题描述】:

我正在尝试递归应用 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++ c++11 templates sfinae enable-if


    【解决方案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】:
      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&lt;B&gt;{}.fun() 无效,所以can_dot_fun&lt; C&lt;B&gt;&gt; &gt; 为假。

      为了简洁起见,此答案使用,但这些片段可以写到(如void_t)。

      【讨论】:

        【解决方案3】:

        首先,我建议您对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 (&amp;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&lt;C&lt;A&gt;&gt;

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

        你看到你得到了零,因为C&lt;A&gt; 中的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&lt;C&lt;A&gt;&gt;::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;
         }
        

        【讨论】:

          【解决方案4】:

          这可以通过两种 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 的大部分内容实际上会在两种情况下共享,您也可以将共享的部分拆分为基础。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-23
            • 2016-06-26
            • 2020-09-07
            • 1970-01-01
            相关资源
            最近更新 更多