2016-04-24 3 views
1

У меня есть шаблон map_n, который применяет функцию N-arity к каждому набору элементов из N входных кортежей для создания нового выходного кортежа. Все входные кортежи должны иметь одинаковую длину (что я, вероятно, должен проверить со статическим утверждением).Частичная специализация шаблона C++ для аргументов non-type

Код работает корректно, за исключением того, что я не смог описать частичную специализацию условий завершения рекурсии общим способом, как показано в следующем фрагменте кода.

#include <tuple> 
#include <cassert> 

namespace impl { 
    // car, cdr, cons implementation 
    // 
    template<unsigned... XS> 
    struct sequence { 
     template<unsigned X> 
     using cons = sequence<X, XS...>; 
    }; 

    template<unsigned start, unsigned end> 
    struct range { 
     static_assert(start < end, "Range: start > end"); 
     using type = typename range<start + 1, end>::type::template cons<start>; 
    }; 

    template<unsigned start> 
    struct range<start, start> { 
     using type = sequence<>; 
    }; 

    template<typename T, unsigned... N> 
    auto select(const T& t, sequence<N...>) { 
     return std::make_tuple(std::get<N>(t)...); 
    } 

} // end namespace impl 

// car, cdr, cons 
// 
// empty list 
// 
constexpr const std::tuple<> empty; 

// car 
// 
template<typename T> 
auto car(const T& t) { return std::get<0>(t); } 

// cdr 
// 
template<typename T, typename R = typename impl::range<1, std::tuple_size<T>::value>::type> 
auto cdr(const T& t) { 
    return impl::select(t, R()); 
} 

// cons 
// 
template<typename X, typename... XS> 
auto cons(X x, const std::tuple<XS...>& t) { 
    return std::tuple_cat(std::make_tuple(x), t); 
} 

namespace impl { 
    // map_n implementation 
    template<typename F, typename... Ts> 
    struct map_n_impl { 
     static auto map(const F& f, const Ts&... t) { 
      return cons(
       f(car(t)...), 
       map_n_impl<F, decltype(cdr(t))...>::map(f, cdr(t)...) 
       ); 
      } 
     }; 

    // NOTE: Need a more general specialization here 
    // 
    template<typename F> 
    struct map_n_impl<F, std::tuple<>, std::tuple<>> { 
     static std::tuple<> map(const F&, const std::tuple<>&, const std::tuple<>&) 
     { 
      return std::make_tuple(); 
     } 
    }; 
} // end namespace impl 

// map_n 
// 
template<typename F, typename... Ts> 
auto map_n(const F& f, const Ts&... t) { 
    return impl::map_n_impl<F, Ts...>::map(f, t...); 
} 


int main(int, const char **) { 
    { 
     auto tup1 = std::make_tuple(1.0, 2.0, 3.0); 
     auto tup2 = std::make_tuple(0.0, 1.0, 2.0); 
     auto r = map_n([](auto x, auto y) { return x - y; }, tup1, tup2); 
     assert(std::get<0>(r) == 1.0); 
     assert(std::get<1>(r) == 1.0); 
     assert(std::get<2>(r) == 1.0); 
    } 

    // { 
    // auto tup1 = std::make_tuple(1.0, 2.0, 3.0); 
    // auto tup2 = std::make_tuple(0.0, 1.0, 2.0); 
    // auto tup3 = std::make_tuple(4.0, 5.0, 6.0); 
    // auto r = map_n([](auto x, auto y, auto z) { return x - y + z; }, tup1, tup2, tupe3); 
    // assert(std::get<0>(r) == 5.0); 
    // assert(std::get<1>(r) == 6.0); 
    // assert(std::get<2>(r) == 7.0); 
    // } 

    return 0; 
} 

ответ

1

Это намного проще, чем то, что вы собираетесь делать. Вам совсем не нужно map_n_impl. Если мы будем придерживаться функционального, рекурсивного подхода - нам нужны две перегрузки map_n: один для всех кортежей не пуст, а один для всех кортежей пуст. Мы будем использовать bool_pack трюк Columbo, чтобы выяснить, если они все пустые или нет:

template <bool... > 
struct bool_pack; 

template <bool... b> 
using all_true = std::is_same<bool_pack<true, b...>, bool_pack<b..., true>>; 

template <class... T> 
using all_empty = all_true<std::is_same<T, std::tuple<>>::value...>; 

А затем просто использовать, что SFINAE два непересекающихся условия:

template<typename F, typename... Ts, 
    std::enable_if_t<!all_empty<Ts...>::value, int*> = nullptr> 
auto map_n(const F& f, const Ts&... t) { 
    return cons(
     f(car(t)...), 
     map_n(f, cdr(t)...) 
     ); 
} 

template<typename F, typename... Ts, 
    std::enable_if_t<all_empty<Ts...>::value, int*> = nullptr> 
auto map_n(const F& , const Ts&... t) { 
    return std::make_tuple(t...); 
} 

tuple Обратите внимание, что ISN» t действительно лучший способ сделать cons/car/cdr в C++ - это не очень cdr -able. Более подходящим будет вложенный pair с.

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

template <size_t... Is, class F, class... Ts> 
auto map_n_impl(std::index_sequence<Is...>, const F& f, const Ts&... ts) { 
    auto call = [&](auto idx){ 
     return f(std::get<idx>(ts)...); 
    }; 

    return std::make_tuple(
     call(std::integral_constant<size_t, Is>{})... 
     ); 
} 

template <class F, class T0, class... Ts> 
auto map_n(const F& f, const T0& t0, const Ts&... ts) { 
    return map_n_impl(
     std::make_index_sequence<std::tuple_size<T0>::value>{}, 
     f, 
     t0, 
     ts...); 
} 
+0

's/decltype (idx) :: value/idx /'. –

+0

@ T.C. Не понял, что это возможно. Круто. – Barry

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