非类型参数的 C++ 可变参数模板偏特化

标签 c++ c++11 tuples variadic-templates

我有一个 map_n 模板,它对 N 个输入元组中的每组元素应用 N 元函数以生成新的输出元组。所有输入元组的长度必须相同(我应该用静态断言检查一下)。

代码工作正常,除了我无法以通用方式编写递归终止条件部分特化,如以下代码片段所示。

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

最佳答案

这比您想要的要容易得多。您根本不需要 map_n_impl。如果我们要坚持使用函数式递归方法 - 我们需要 map_n 的两个重载:一个用于所有元组非空,一个用于所有元组为空。我们将使用 Columbo 的 bool_pack 技巧来判断它们是否全部为空:

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 并不是在 C++ 中实现 cons/car/cdr 的最佳方式- 它不是很 cdr 能用。更合适的是嵌套的

您还可以使用索引序列技巧一次性构建整个元组。这里有点烦人,因为我们需要以不同方式解压两个参数包,因此需要额外的 call lambda。可能有更好的方法来做到这一点:

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

关于非类型参数的 C++ 可变参数模板偏特化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36826015/

相关文章:

c++ - 在笛卡尔平面上旋转 X、Y 坐标的问题

c++ - clang 的自定义分配器上的额外移动结构

C++ 值初始化自定义容器的项目

c++ - 部分避免类模板参数(从构造函数参数推导)

c++ - 验证 EXE 上的 Authenticode 签名 - 没有 CAPICOM 的 C++

c++ - C++的SVM教程

c++ - Microsoft C++ 异常:内存位置处的 std::regex_error

arrays - Swift 数组初始值设定项语法类型不匹配

c++ - 将变体数组转换为 std::tuple

python - 序列中值对(2 项元组)的所有可能组合 - PYTHON 2.7