键入敏感元组访问者

时间:2012-08-07 10:11:40

标签: c++ c++11 tuples

假设我有std::tuple

等类型组成
struct A {
  static void tip();
};

struct B {
  static void tip();
};

struct Z {
};

std::tuple<const A&,const B&,const Z&> tpl;

是的,我需要单独的AB。 (::tip()的实现因每种类型而异。)我尝试实现的是一个类型敏感的“访问者”,它从头到尾迭代遍历元组。在访问T类型的特定元素时,应根据T是否具有::tip()方法调用函数。在上面的简单示例中,仅AB已实施::tip()Z未实施。因此,迭代器应该使用::tip()方法和另一个函数调用两次函数的函数。

以下是我提出的建议:

template< int N , bool end >
struct TupleIter
{
  template< typename T , typename... Ts >
  typename std::enable_if< std::is_function< typename T::tip >::value , void >::type
  static Iter( const T& dummy , const std::tuple<Ts...>& tpl ) {
    std::cout << "tip\n";
    std::get<N>(tpl); // do the work
    TupleIter<N+1,sizeof...(Ts) == N+1>::Iter( std::get<N+1>(tpl) , tpl );
  }

  template< typename T , typename... Ts >
  typename std::enable_if< ! std::is_function< typename T::tip >::value , void >::type
  static Iter( const T& dummy , const std::tuple<Ts...>& tpl ) {
    std::cout << "no tip\n";
    std::get<N>(tpl); // do the work
    TupleIter<N+1,sizeof...(Ts) == N+1>::Iter( std::get<N+1>(tpl) , tpl );
  }
};


template< int N >
struct TupleIter<N,true>
{
  template< typename T , typename... Ts >
  static void Iter( const std::tuple<Ts...>& tpl ) {
    std::cout << "end\n";
  }
};

我在迭代器位置使用元素类型的dummy实例,并通过enable_if决定调用哪个函数。不幸的是,这不起作用/不是一个很好的解决方案:

  1. 编译器抱怨递归实例化
  2. const T& dummy不是一个干净的解决方案
  3. 我想知道enable_if是否是做出决定的正确策略,以及如何递归迭代std::tuple捕获第一种类型并将所有剩余参数保持在生命状态。仔细阅读How to split a tuple?,但不做任何决定。

    如何在C ++ 11中以正确和可移植的方式实现这样的东西?

2 个答案:

答案 0 :(得分:4)

嗯,这比我想象的要难,但this有效。

你做错了/我修改过的一些事情:

  1. 您无法对此进行评估:std::is_function< typename T::tip >::value,因为T::tip不是类型。即使可以评估这一点,T::tip不存在时会发生什么?替补仍然会失败。
  2. 由于您使用const引用作为元组的内部类型,因此在尝试查找其中的tip成员之前必须先清除它们。通过清洁我的意思是删除const并删除引用。
  3. 虚拟类型的东西不是一个好主意,没有必要使用该参数。您可以使用std::tuple_element来实现相同的功能,它从元组中检索第i个类型。
  4. 我将TupleIter的模板参数修改为以下内容,即:
  5. TupleIter处理索引类型,在大小为n的元组内。”

    template<size_t index, size_t n> 
    struct TupleIter;
    

    整个代码是这样的:

    #include <tuple>
    #include <iostream>
    #include <type_traits>
    
    struct A {
      static void tip();
    };
    
    struct B {
      static void tip();
    };
    
    struct Z {
    };
    
    // Indicates whether the template parameter contains a static member named tip.
    template<class T>
    struct has_tip {
        template<class U>
        static char test(decltype(&U::tip));
    
        template<class U>
        static float test(...);
    
        static const bool value = sizeof(test<typename std::decay<T>::type>(0)) == sizeof(char);
    };
    
    // Indicates whether the n-th type contains a tip static member
    template<size_t n, typename... Ts>
    struct nth_type_has_tip {
        static const bool value = has_tip<typename std::tuple_element<n, std::tuple<Ts...>>::type>::value;
    };
    
    // Generic iteration
    template<size_t index, size_t n>
    struct TupleIter
    {
      template< typename... Ts >
      typename std::enable_if< nth_type_has_tip<index, Ts...>::value , void >::type
      static Iter(const std::tuple<Ts...>& tpl) 
      {
        std::cout << "tip\n";
        TupleIter<index + 1, n>::Iter(tpl );
      }
    
      template< typename... Ts >
      typename std::enable_if< !nth_type_has_tip<index, Ts...>::value , void >::type
      static Iter(const std::tuple<Ts...>& tpl) {
        std::cout << "no tip\n";
        TupleIter<index + 1, n>::Iter(tpl );
      }
    };
    
    // Base class, we've reached the tuple end
    template<size_t n>
    struct TupleIter<n, n>
    {
      template<typename... Ts >
      static void Iter( const std::tuple<Ts...>& tpl ) {
        std::cout << "end\n";
      }
    };
    
    // Helper function that forwards the first call to TupleIter<>::Iter
    template<typename... Ts>
    void iterate(const std::tuple<Ts...> &tup) {
        TupleIter<0, sizeof...(Ts)>::Iter(tup);
    }
    
    int main() {
        A a;
        B b;
        Z z;
        std::tuple<const A&,const B&,const Z&> tup(a,b,z);
        iterate(tup);
    }
    

答案 1 :(得分:2)

这是另一个问题,非常类似于mfontanini答案,但展示:

boost :: fusion :: for_each(而不是手动遍历元组) 使用基于表达式的SFINAE方法实现has_type的变体,我觉得比通常的大小技巧更容易理解。

#include <boost/tuple/tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
#include <boost/fusion/algorithm.hpp>

#include <iostream>

    struct nat // not a type
    {
    private:
        nat(); 
        nat(const nat&);
        nat& operator=(const nat&);
        ~nat();
    };

    template <typename T>
    struct has_tip
    {
        static auto has_tip_imp(...) -> nat;

        template <typename U>
        static auto has_tip_imp(U&&) -> decltype(U::tip());

        typedef decltype(has_tip_imp(std::declval<T>())) type;
        static const bool value = !std::is_same<type, nat>::value;
    };


    struct CallTip
    {
        template<typename T>
        typename std::enable_if<has_tip<T>::value>::type
        operator()(T& t) const
        {
            std::cout << "tip\n";
            T::tip();
        }

        template<typename T>
        typename std::enable_if<!has_tip<T>::value>::type
        operator()(T& t) const
        {
            std::cout << "no tip\n";
            return;
        }
    };

struct A {
    static void tip(){}
};

struct B {
    static void tip(){}
};

struct Z {
};

int main()
{
    A a;
    B b;
    Z z;
    boost::tuple<const A&,const B&,const Z&> tpl(a, b, z);
    boost::fusion::for_each(tpl, CallTip());
}

请注意,如果您的编译器支持可变参数模板,您可以通过包含#include<boost/fusion/adapted/std_tuple.hpp>

来使用std :: tuple而不是boost :: for_each中的boost :: tuple

编辑: 正如Xeo在评论中指出的那样,通过完全删除特征has_tip并简单地转发给一个小调用助手,可以简化表达式 - SFINAE方法。 最终的代码真的很整洁!

#include <boost/tuple/tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
#include <boost/fusion/algorithm.hpp>

#include <iostream>

struct CallTip
{
    template<typename T>
    void operator()(const T& t) const
    {
        call(t);
    }

    template<class T>
    static auto call(const T&) -> decltype(T::tip())
    { 
        std::cout << "tip\n";
        T::tip(); 
    }

    static void call(...)
    {
        std::cout << "no tip\n";
    }
};

struct A {
    static void tip(){}
};

struct B {
    static void tip(){}
};

struct Z {
};

int main()
{
    A a;
    B b;
    Z z;
    boost::tuple<const A&,const B&,const Z&> tpl(a, b, z);
    boost::fusion::for_each(tpl, CallTip());
}