【问题标题】:Type sensitive tuple visitor类型敏感元组访问者
【发布时间】:2012-08-07 10:11:39
【问题描述】:

假设我有一个 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&amp; dummy 不是一个干净的解决方案

我想知道enable_if 是否是做出决定的正确策略,以及如何递归遍历std::tuple 捕获第一种类型并保持所有剩余参数处于重要状态。通读How to split a tuple?,但它没有做任何决定。

如何在 C++11 中以正确且可移植的方式实现这样的事情?

【问题讨论】:

  • 我怀疑这个或类似的东西可能已经在 Boost.Fusion 中实现了。可能值得检查。

标签: c++ c++11 tuples


【解决方案1】:

嗯,这比我预期的要难,但this 有效。

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

  1. 你不能评估这个:std::is_function&lt; typename T::tip &gt;::value,因为T::tip 不是一个类型。即使可以评估,当T::tip 不存在时会发生什么?替换仍然会失败。
  2. 由于您使用 const 引用作为元组的内部类型,因此您必须先清理它们,然后再尝试在其中找到尖端成员。通过清理,我的意思是删除 const 并删除引用。
  3. 那个虚拟类型的东西不是一个好主意,没有必要使用那个参数。您可以使用 std::tuple_element 实现相同的目的,它从元组中检索第 i 个类型。
  4. 我将TupleIter的模板参数修改为如下,意思是:

"TupleIter 在大小为 n 的元组内处理第 index 个类型"。

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);
}

【讨论】:

  • 我已经对代码进行了一些注释,因此您可以了解每个帮助器类的作用。我已经修改了ideone链接。
  • 那些是省略号。当编译器可以调用函数的多个重载时,采用省略号的函数将是最差匹配。所以在这种情况下,这些是为了让编译器总是选择第一个重载(返回一个字符的那个),因为它是一个更好的匹配(它比省略号更专业)。
  • 为什么test&lt;&gt;(0) 调用test(decltype(&amp;U::tip)) 而不是省略号版本?例如,如果 U==A 扩展到什么 decltype(&amp;U::tip)?哦.. 只​​是读你的评论。这是最糟糕的比赛!并且可以使用0 初始化对函数的引用。这就是为什么它调用char test&lt;&gt;
  • 完全正确:D。因为它比椭圆更专业。这就是我使用省略号的原因,everything 比省略号函数更专业。 &amp;A::tipvoid(*)() 类型,所以 decltype(&amp;U::tip) 就是这样(当 U==A 时)。
  • @mfontanini:由于无法保证 ideone 将永远保留您的要点,您能否将其复制/粘贴到您的答案中(也许作为附录)?
【解决方案2】:

这是对这个问题的另一种看法,与 mfontanini 的答案非常相似,但展示的是:

boost::fusion::for_each(而不是手动迭代元组)。
使用基于表达式的 SFINAE 方法实现 has_type 的变体,我觉得比通常的 sizeof 技巧更容易遵循。

#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());
}

请注意,如果您的编译器支持可变参数模板,您可以在 fusion::for_each 中使用 std::tuple 而不是 boost::tuple,方法是包含 #include&lt;boost/fusion/adapted/std_tuple.hpp&gt;

编辑: 正如 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());
}

【讨论】:

  • +1,我还建议使用表达式 SFINAE 方法,尽管我什至不会把它放在一个额外的特征中,而是简单地从 operator() 转发到一个小助手,比如 @ 987654321@.
  • @Xeo 美丽!我编辑了答案,因为如此巧妙的解决方案不值得被埋没在评论中!我只是更改了 int/long 重载,因为使用“优先机制”int/long 有点困扰我。 (我不知道正确的术语)这似乎太牵强,难以快速理解,就像最初的 SFINAE sizeof 技巧一样。我用包罗万象的 .../const T& 代替了它,它看起来更清晰一些。你知道这种方法是否有缺点吗?
  • 是“整体推广”。 ;) 我知道的唯一缺点显然是你需要一个对象来传递,而不是仅仅传递0。不过,我非常喜欢int/long 方法,正如here 所展示的那样。
猜你喜欢
  • 2017-10-25
  • 1970-01-01
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-12
  • 2019-02-15
  • 1970-01-01
相关资源
最近更新 更多