Можно ли генерировать типы со всеми комбинациями аргументов шаблона?

StackOverflow https://stackoverflow.com/questions/1492204

Вопрос

У меня есть шаблонный класс

template<class U, class V, class W> 
class S
{
//... implementations 
};

и некоторые реализации стандартных типов для типа U, V и W:

typedef boost::mpl::vector<U0, U1> u_types;
typedef boost::mpl::vector<V0, V1, V2, V3, V4> u_types;
typedef boost::mpl::vector<W0, W1, W2, W3, W4> w_types;

Я хочу протестировать класс S со всеми возможными комбинациями аргументов шаблона,

typedef boost::mpl::vector<
    S<U0,V0,W0>, 
    S<U0,V0,W1>,
    // ...
    S<U1,V4,W4>,
    > s_types;

так:

boost::mpl::for_each<s_types>(test_func).

Единственная проблема в том, что есть 2 ** 5 ** 5 = 50 комбинаций, которые я не хочу вводить одну за другой.

Есть ли способ сгенерировать все комбинации(s_types) с Boost::mpl или Boost.Preprocessor?

Спасибо.


Добавлены мои первоначальные неудачные попытки:

Я пытался прибегнуть к индексам (следовательно, определяя u_types и тому подобное) и частичной специализации шаблонов, подобной этой.

namespace wrapper
{
  template <int Uidx, int Vidx, int Widx> 
  struct S_Wrapper
  {
    typedef S<Uidx, Vidx, Widx> type;

    S_Wrapper() // auto test in the ctor
    {
      cout << "test result = " << test(type());
    }

    // test with S<Uidx, Vidx, Widx>
    static bool test(type t)
    {
      // implementations
    }

    // get stuck here, 
    S_Wrapper<Uidx-1, Vidx, Widx> s; // temp varible to invoke recursive-ness
    // what else to complete all recursive path?
  };

  // specializations       
  template <0, 0, 0> 
  struct S_Wrapper
  {
    typedef S<0, 0, 0> type;

    // test with S<Uidx, Vidx, Widx>
    //
    static bool test(type t)
    {
      // implementations
    }  
  };

  // get stuck here, too
  // what other specializations are ?
  // other specializations
}

затем с

wrapper::S_Wrapper< 
  mpl::size<u_types>::type::value, 
  mpl::size<v_types>::type::value, 
  mpl::size<w_types>::type::value
> s; 

все типы S должны быть сгенерированы и протестированы;

Однако мне не удалось охватить всю комбинацию, определив

1) соответствующие специализации и
2) триггеры рекурсивности в структуре S_Wrapper

Все мои испытания заканчивались либо частичным охватом комбинаций во время выполнения, либо сбоем вывода во время компиляции.

Есть предположения?


Решение

Вдохновленный Матье, я придумал шаблонный класс. Combine чтобы я мог достичь своей цели в двух строках:

typedef Combine<
            u_types,
            v_types,
            w_type,
            print_typeid
        >::Generate<> base_generator_type;
base_generator_type::Run();

который будет печатать все сгенерированные типы.


Код

// example test implementation
struct print_typeid
{
    template<
        class U, 
        class V, 
        class W
    >
    static void run()
    {
        // print the typeinfo
        std::cout 
            <<  total_recursions << ":"
            << typeid(U).name() << ","
            << typeid(V).name() << ","
            << typeid(W).name()
            << std::endl;  
    }            
}; 

// solution implemented in one wrapper class
namespace argument_combination
{
    using boost::is_same;
    using boost::mpl::begin;
    using boost::mpl::end;
    using boost::mpl::next;        
    using boost::mpl::if_;  
    using boost::mpl::deref;    

    unsigned int total_recursions = 0;

    struct end_of_recursion_tag 
    {
        static void Run()
        {
            std::cout << "end of " 
                 << total_recursions 
                 << " recursions\n"
                ;
        }
    };

    template <
        class UTypes,    // Forward Sequence, e.g. boost::mpl::vector
        class VTypes,    // Forward Sequence, e.g. boost::mpl::vector
        class WTypes,    // Forward Sequence, e.g. boost::mpl::vector
        class TestFunc  // class type that has a nested templated run() member function
    >
    struct Combine
    {
        // forward declaration
        template <
            class UIterator,
            class VIterator,
            class WIterator 
        >   
        class Generate;

        // this class implements recursion body 
        template <
            class UIterator,
            class VIterator,
            class WIterator
        >            
        struct Next
        {
            // u_begin is not necessary ;)
            // it would be cheaper not to pre-declare all of them since we force evaluation
            // however this dramatically increase the readability
            typedef typename begin<VTypes>::type v_begin;
            typedef typename begin<WTypes>::type w_begin;

            typedef typename end<UTypes>::type u_end;
            typedef typename end<VTypes>::type v_end;
            typedef typename end<WTypes>::type w_end;

            typedef typename next<UIterator>::type u_next;
            typedef typename next<VIterator>::type v_next;
            typedef typename next<WIterator>::type w_next;

            typedef typename if_< is_same<typename w_next, w_end>,
                                typename if_< is_same<v_next, v_end>,
                                    typename if_< is_same<u_next, u_end>,
                                        end_of_recursion_tag,
                                        Generate< 
                                            u_next, 
                                            v_begin, 
                                            w_begin 
                                        >
                                    >::type,
                                    Generate< 
                                        UIterator, 
                                        v_next,
                                        w_begin 
                                    >
                                >::type,
                                Generate< 
                                    UIterator, 
                                    VIterator, 
                                    w_next
                                    >
                            >::type type;
        };

        //  this class run test on generated types in thos round and go to next*/
        template <
            class UIterator = typename begin<UTypes>::type,
            class VIterator = typename begin<VTypes>::type,
            class WIterator = typename begin<WTypes>::type
        >
        struct Generate
        {
            //  generate <<next>> target type         
            typedef typename Next<
                    UIterator, 
                    VIterator, 
                    WIterator 
                >::type next_type;

            static void Run()
            {
                // increment recursion counter                
                ++total_recursions;

                // test on the generated types of this round of recursion
                TestFunc::run<
                     typename deref<UIterator>::type,
                     typename deref<VIterator>::type,
                     typename deref<WIterator>::type
                 >();

                // go to the next round of recursion
                next_type::Run();
            }
        };
    };

}//  namespace argument_combination
Это было полезно?

Решение

Если вам действительно нужно сгенерировать вектор всех возможных решений и затем протестировать их, вам придется использовать препроцессор, чтобы сгенерировать их все за вас.

Однако другое решение могло бы заключаться в использовании генератора:класс-оболочка, который будет создавать экземпляры всех ваших решений и тестировать их.Возможно, вам захочется проконсультироваться с Генераторами иерархии Локи (подробно описанными в книге).

// never remember where they put boost::same_type :x
#include <boost/mpl/if.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/mpl/begin.hpp>
#include <boost/mpl/end.hpp>
#include <boost/mpl/next.hpp>

using namespace boost::mpl;

struct None
{
   static void test() {}
};

template <class UIterator, class UTypes,
          class VIterator, class VTypes,
          class WIterator, class WTypes>
class Generator;

template <class UIterator, class UTypes,
          class VIterator, class VTypes,
          class WIterator, class WTypes>
struct Next
{
  // u_begin is not necessary ;)
  // it would be cheaper not to pre-declare all of them since we force evaluation
  // however this dramatically increase the readability
  typedef typename begin<VIterator>::type v_begin;
  typedef typename begin<WIterator>::type w_begin;

  typedef typename next<UIterator>::type u_next;
  typedef typename next<VIterator>::type v_next;
  typedef typename next<WIterator>::type w_next;

  typedef typename end<UIterator>::type u_end;
  typedef typename end<VIterator>::type v_end;
  typedef typename end<WIterator>::type w_end;


  typedef if_< boost::same_type<w_next, w_end>,
               if_< boost::same_type<v_next, v_end>,
                    if_< boost::same_type<u_next, u_end>,
                         None,
                         Generator< u_next, UTypes,
                                    v_begin, VTypes,
                                    w_begin, WTypes >
                    >,
                    Generator< UIterator, UTypes,
                               v_next, VTypes,
                               w_begin, WTypes >
                >,
                Generator< UIterator, UTypes,
                           VIterator, VTypes,
                           w_next, WTypes>
           >::type type;
};

template <class UIterator, class UTypes,
          class VIterator, class VTypes,
          class WIterator, class WTypes>
struct Generator
{
   typedef S< deref<UIterator>::type,
              deref<VIterator>::type,
              deref<WIterator>::type > S_type;

   typedef Next<UIterator, UTypes,
                VIterator, VTypes,
                WIterator, WTypes>::type next_type;

   static void test()
   {
     // test my variation of S
     S_Type my_S;
     test_func(my_S);

     // test the variations of my next and its next and... you get the idea :)
     next_type::test();
   }
};

// And finally
int main(int argc, char* argv[])
{
  typedef Generator< begin<u_types>::type, u_types,
                     begin<v_types>::type, v_types,
                     begin<w_types>::type, w_types > base_generator_type;

  base_generator_type::test();
}

Отказ от ответственности:этот код не был скомпилирован, и в нем могут отсутствовать некоторые директивы include/typename/use...тем не менее я надеюсь, что вы поняли мою точку зрения.

Если вы имеете какое-либо представление о том, что такое шаблоны проектирования, то они очень похожи на «декоратор» или «композитный» дизайн в плане добавления еще одного раунда тестов на каждом уровне шага.

Еще хотелось бы отметить, что это занимает более 50 строк кода...но, по крайней мере, он будет хорошо расти вместе с векторами :)

Другие советы

Дорогой Матье, ваше решение работает нормально.Для моей структуры мне нужен был более общий способ реализации комбинации типов, поэтому я разработал что-то другое, что, похоже, работает.Предлагаю здесь свою реализацию.Я думаю, что это также может быть более ориентировано на MPL, если вы примете во внимание концепцию представления mpl.Он обеспечивает комбинацию последовательностей типов в виде специального комбинированного представления и комбинированных итераторов:

template < class Seq >
class combine_view {
    typedef typename mpl::transform<Seq, mpl::begin<_1> >::type Pos_begin;
    typedef typename mpl::transform<Seq, mpl::end<_1> >::type   Pos_end;
public:
    typedef combine_iterator<Seq, Pos_begin> begin;
    typedef combine_iterator<Seq, Pos_end>  end;
    typedef combine_view type;
};

Это создает новую последовательность прямого доступа из последовательности последовательностей произвольного доступа.Комбайн_итератор, предоставляющий начало и конец последовательности, должен знать как последовательности, так и итераторы, обеспечивающие позицию, как, например, в этой реализации:

 template < typename Seq, typename Itrs >
 struct combine_iterator {
     typedef mpl::forward_iterator_tag category;
     typedef Seq  seq;
     typedef typename transform <
         Itrs,
         deref<_1>
       >::type
     type;
 };

Теперь вам нужно указать boost mpl, как перейти к следующей позиции, используя операцию mpl::next.

namespace boost {
namespace mpl {

template <class Seq, class Pos>
struct next< combine_iterator<Seq, Pos> > {
    typedef typename SequenceCombiner<Seq,Pos>::next next_Pos;
    typedef combine_iterator< Seq, next_Pos > type;
};

} // mpl
} // boost

Наконец, трюк с объединителем можно реализовать с помощью mpl::fold, как в этом классе:

template <class Seq, class ItrSeq>
class SequenceCombiner {

   template < class _Seq = mpl::vector<int_<1> > >
   struct StateSeq {
       typedef typename pop_front<_Seq>::type sequence;
       typedef typename mpl::at< _Seq, int_<0> >::type state;
       typedef _Seq type;
   };

   template < class _Seq, class _State >
   struct set_state {
       typedef StateSeq< typename push_front<_Seq, _State >::type > type;
   };

   struct NextOp {

       template < typename Out, typename In, typename Enable = typename Out::state >
       class apply {
           typedef typename Out::sequence seq;
           typedef typename Out::state new_state;
           typedef typename mpl::at<In,int_<0> >::type in_seq;
        typedef typename mpl::at<In,int_<1> >::type in_itr;

        typedef typename mpl::push_back<seq, in_itr >::type new_seq;
    public:
        typedef typename set_state<new_seq, int_<0> >::type type;
    };

    template < typename Out, typename In >
    class apply<Out,In,mpl::int_<1> > {
        typedef typename Out::sequence seq;
        typedef typename Out::state state;
        typedef typename mpl::at<In,int_<0> >::type in_seq;
        typedef typename mpl::at<In,int_<1> >::type in_itr;

        typedef typename mpl::begin<in_seq>::type Itr_begin;
        typedef typename mpl::next<in_itr>::type  Itr_next;
        typedef typename mpl::end<in_seq>::type   Itr_end;

        typedef typename mpl::if_< boost::is_same<Itr_next,Itr_end>,
        typename mpl::push_back<seq,Itr_begin>::type,
        typename mpl::push_back<seq,Itr_next>::type
        >::type
        new_seq;

        typedef typename mpl::if_< boost::is_same<Itr_next,Itr_end>,
        mpl::int_<1>,
        mpl::int_<0>
        >::type
        new_state;
    public:
        typedef typename set_state<new_seq, new_state>::type type;
    };
};

typedef typename mpl::fold<
                            typename mpl::zip_view< mpl::vector<Seq, ItrSeq > >::type,
                            StateSeq<>,
                            NextOp
                          >::type
StateResult;

public:

typedef typename mpl::if_< boost::is_same< typename StateResult::state, int_<1> >,
                           typename mpl::transform< Seq, mpl::end<_1> >::type,
                           typename StateResult::sequence >::type
next;
};

Позвольте мне показать вам, как использовать полученное новое представление последовательности в тестовом приложении.

struct A {};
struct B {};
struct C {};
struct D {};
struct E {};
struct F {};
struct G {};
struct H {};
struct I {};

namespace {        
struct PrintTypeId {
    template <class T>
    void operator()(T) const
    { std::cout << typeid(T).name() << "  "; }
};
struct PrintSeq {
    template < typename T >
    void operator()(T) {
        mpl::for_each<T>( PrintTypeId() );
        std::cout << "\n";
    }
};
}

int main() {
    BEGIN_TESTING( Mpl Sequence Combiner Test);

    typedef mpl::vector<A,B,C>   seq1;
    typedef mpl::vector<D,E,F>   seq2;
    typedef mpl::vector<G,H,I>   seq3;

    typedef mpl::combine_view< mpl::vector<seq1,seq2,seq3> > cv;
    mpl::for_each< cv >( PrintSeq() );

    END_TESTING;
}

Результат должен быть примерно таким:

..:: Testing Mpl Sequence Combiner Test ::..
1A  1D  1G  
1B  1D  1G  
1C  1D  1G  
1A  1E  1G  
1B  1E  1G  
1C  1E  1G  
1A  1F  1G  
1B  1F  1G  
1C  1F  1G  
1A  1D  1H  
1B  1D  1H  
1C  1D  1H  
1A  1E  1H  
1B  1E  1H  
1C  1E  1H  
1A  1F  1H  
1B  1F  1H  
1C  1F  1H  
1A  1D  1I  
1B  1D  1I  
1C  1D  1I  
1A  1E  1I  
1B  1E  1I  
1C  1E  1I  
1A  1F  1I  
1B  1F  1I  
1C  1F  1I  

Спасибо за внимание.

Андреа Ригони Гарола

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top