2013-04-01 4 views
7

Моя цель сделать что-то такое, что, например,шаблоны VARIADIC: производить кортеж пар соседних элементов

pairs<1,2,3,4>() 

имеет возвращаемый тип

std::tuple<some_other_type<1,2>, some_other_type<2,3>, some_other_type<3,4>> 

Я интересно, если это вообще возможно с метапрограммированием шаблонов C++ и как это можно сделать. Для фактического создания значения кажется, что я могу использовать tuple_cat для рекурсивной конкатенации вывода, но мне трудно выразить возвращаемый тип, поскольку он сам является вариативным и эффективно функцией числа параметров шаблона. Усложняя ситуацию, если я пошел по маршруту tuple_cat, мне кажется, что мне также придется перегружать функцию, чтобы взять кортеж, чтобы объединиться, а конкатенация произойдет во время выполнения, а не во время компиляции. Я здесь, на дикой гусиной погоне?

+0

+1 Это, вероятно, достижима с помощью класса ... –

ответ

12

Вот как это сделать. Учитывая ваш шаблон класса some_other_type:

template<int I, int J> 
struct some_other_type { }; 

И дал некоторые механизмы скрыты в detail имен:

namespace detail 
{ 
    template<int... Is> 
    struct pairs { }; 

    template<int I, int J> 
    struct pairs<I, J> 
    { 
     using type = std::tuple<some_other_type<I, J>>; 
    }; 

    template<int I, int J, int... Is> 
    struct pairs<I, J, Is...> 
    { 
     using type = decltype(std::tuple_cat(
       std::tuple<some_other_type<I, J>>(), 
       typename pairs<J, Is...>::type())); 
    }; 
} 

Вы могли бы обеспечить простую функцию, которая создает экземпляр шаблона вспомогательный класс:

template<int... Is> 
typename detail::pairs<Is...>::type pairs() 
{ 
    return typename detail::pairs<Is...>::type(); 
} 

И вот как вы его использовали (и тестовый пример):

#include <type_traits> 

int main() 
{ 
    auto p = pairs<1, 2, 3, 4>(); 

    // Won't fire! 
    static_assert(
     std::is_same< 
      decltype(p), 
      std::tuple< 
       some_other_type<1,2>, 
       some_other_type<2,3>, 
       some_other_type<3,4>> 
      >::value, 
      "Error!"); 
} 

И, наконец, вот live example.


УЛУЧШЕНИЯ: (почему написание <1, 2, 3, 4>, когда можно было бы написать <1, 5>)?

Также возможно расширить вышеупомянутое решение, чтобы не требовалось вручную записывать каждое число между минимумом и максимумом в качестве аргумента шаблона pairs(). Учитывая дополнительное оборудование ниже, снова спрятался в detail имен:

namespace detail 
{ 
    template <int... Is> 
    struct index_list { }; 

    template <int MIN, int N, int... Is> 
    struct range_builder; 

    template <int MIN, int... Is> 
    struct range_builder<MIN, MIN, Is...> 
    { 
     typedef index_list<Is...> type; 
    }; 

    template <int MIN, int N, int... Is> 
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...> 
    { }; 

    // Meta-function that returns a [MIN, MAX) index range 
    template<int MIN, int MAX> 
    using index_range = typename range_builder<MIN, MAX>::type; 

    template<int... Is> 
    auto pairs_range(index_list<Is...>) -> decltype(::pairs<Is...>()) 
    { 
     return ::pairs<Is...>(); 
    } 
} 

можно определить вспомогательную функцию pairs_range(), которая принимает 2 аргумента шаблона, определяющие диапазон [begin, end) - где end не входит, в стиле Стандартная библиотека:

template<int I, int J> 
auto pairs_range() -> decltype(pairs_range(detail::index_range<I, J>())) 
{ 
    return pairs_range(detail::index_range<I, J>()); 
} 

И это, как можно было бы использовать его (в том числе теста):

int main() 
{ 
    // Won't fire! 
    static_assert(
     std::is_same< 
      decltype(pairs_range<1, 5>()), 
      decltype(pairs<1, 2, 3, 4>()) 
      >::value, 
      "Error!"); 
} 

И еще раз, вот live example.

4

Вот моя версия этого (live here), 100% времени компиляции, возвращая новый список параметров, как тип (не функция возврата):

Во-первых, давайте определим наши результирующие структуры:

template<int a, int b> 
struct tpair 
{ 
}; 

template<typename... p> 
struct final_ 
{ 
}; 

Ключевым моментом является установка пакетов параметров. Вот структура, которая будет делать работу:

template<typename a, typename b> 
struct concat 
{ 
}; 

template<typename a, typename... b> 
struct concat<a, final<b...>> 
{ 
    typedef final_<a,b...> type; 
}; 

Теперь структура используется для «pairize» список. Обычно она не будет выполнена с нечетным числом значений:

template<int a, int b, int... values> 
struct pairize 
{ 
    // Choose one of the following versions: 
    // First version: only non-overlapping pairs : (1,2) (3,4) ... 
    typedef typename concat<tpair<a,b>, typename pairize<values...>::type>::type type; 
    // Second version: overlapping pairs : (1,2) (2,3) (3,4)... 
    typedef typename concat<tpair<a,b>, typename pairize<b,values...>::type>::type type; 
}; 

template<int a, int b> 
struct pairize<a,b> 
{ 
    typedef final_<tpair<a,b>> type; 
}; 

В живом примере имеется также коды вывода имени всех типов в параметрах обновления к консоли, с декодированием, как тест (был смешнее, чем трюк неполного типа).

+0

Обратите внимание, что вам не хватает любой другой пары (он также хочет 2-3 и 4-5). –

+0

Вы правы, я слишком быстро прочитал этот вопрос. Дайте мне время, чтобы исправить это;) – Synxis

+0

Я думаю, что это исправление 2 символов ;-) –

3

А теперь, давайте попробуем с indices и без рекурсии (за исключением, конечно, для индексов):

#include <tuple> 

template< std::size_t... Ns > 
struct indices 
{ 
    typedef indices< Ns..., sizeof...(Ns) > next; 
}; 

template< std::size_t N > 
struct make_indices 
{ 
    typedef typename make_indices< N - 1 >::type::next type; 
}; 

template<> 
struct make_indices<0> 
{ 
    typedef indices<> type; 
}; 

template< std::size_t, std::size_t > 
struct sometype {}; 

template< typename, typename, typename > 
struct make_pairs; 

template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is > 
struct make_pairs< indices<Ns...>, indices<Ms...>, indices<Is...> > 
{ 
    using type = decltype(std::tuple_cat(
    std::declval< typename std::conditional< Is % 2 == 1, 
              std::tuple< sometype< Ns, Ms > >, 
              std::tuple<> >::type >()... 
)); 
}; 

template< std::size_t... Ns > 
using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >, 
       typename make_indices< sizeof...(Ns) + 1 >::type >::type; 

int main() 
{ 
    static_assert(std::is_same< pairs<1,2,4,3,5,9>, 
    std::tuple< sometype<1,2>, sometype<4,3>, sometype<5,9> > >::value, "Oops"); 
} 

(ОК, я обманул немного: std::tuple_cat может быть рекурсивным сама;)


Обновление: ОК, я должен был внимательно прочитать вопрос. Вот версия, которая производит желаемый результат (indices/make_indices, как указано выше):

template< std::size_t, std::size_t > 
struct sometype {}; 

template< typename, typename, typename > 
struct make_pairs; 

template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is > 
struct make_pairs< indices<Ns...>, indices<Ms...>, indices<Is...> > 
{ 
    using type = decltype(std::tuple_cat(
    std::declval< typename std::conditional< Is != 0 && Is != sizeof...(Is) - 1, 
              std::tuple< sometype< Ns, Ms > >, 
              std::tuple<> >::type >()... 
)); 
}; 

template< std::size_t... Ns > 
using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >, 
       typename make_indices< sizeof...(Ns) + 1 >::type >::type; 

int main() 
{ 
    static_assert(std::is_same< pairs<1,2,3,4>, 
    std::tuple< sometype<1,2>, sometype<2,3>, sometype<3,4> > >::value, "Oops"); 
} 
+0

+1. После двадцати минут рассмотрения этого вопроса я почти могу понять, как и почему он работает. Но я бы никогда не смог понять это сам. Очень интересная техника, мне нужно хорошо это изучить. –

+0

@ AndyProwl Это может показаться странным, но бывают случаи, когда крайне важно избежать рекурсии. Обычно это не важно, пока вы просто вычисляете типы, но когда рекурсия включает в себя генерацию кода, разница огромна! Вот почему мне нравятся такие вопросы и почему я использую их для обучения, даже если это занимает немного больше, чем простая рекурсивная версия. –

+0

Действительно, это очень интересно. Я должен практиковать это больше. Я помню эту технику, потому что вы использовали ее уже [в другом ответе] (http://stackoverflow.com/questions/15411022/how-do-replace-a-tuple-element-at-compile-time/15412010#15412010), но мне никогда не приходило в голову, что он может быть использован здесь. И, немного подумав об этом, кажется, что его можно использовать практически везде. Если у вас есть еще несколько примеров проблем, в которых эта идиома приносит «естественное» решение, не стесняйтесь поднять их, я буду благодарен;) –

Смежные вопросы