2

假设我有一个std::tuple由像这样的类型组成

struct A {
  static void tip();
};

struct B {
  static void tip();
};

struct Z {
};

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

是的,我需要单独A的 , B。(::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不是一个干净的解决方案

我想知道是否enable_if是正确的决策策略,以及如何递归迭代std::tuple捕获第一种类型并将所有剩余的参数保持在重要状态。通读如何拆分元组?但它不做任何决定。

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

4

2 回答 2

4

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

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

  1. 您无法评估 this: std::is_function< typename T::tip >::value,因为T::tip它不是类型。即使可以评估,当T::tip不存在时会发生什么?替换仍然会失败。
  2. 由于您使用 const 引用作为元组的内部类型,因此您必须先清理它们,然后再尝试在其中找到提示成员。通过清理我的意思是删除 const 并删除引用。
  3. 那个 dummy 类型的东西不是一个好主意,没有必要使用那个参数。您可以使用 来实现相同的目的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);
}
于 2012-08-07T12:12:09.793 回答
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<boost/fusion/adapted/std_tuple.hpp>

编辑:正如 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());
}
于 2012-08-07T15:24:14.637 回答