简体   繁体   English

在C ++中递归地使用参数包

[英]Consuming parameter packs recursively in C++

I want to come up with a Lisp-like cons list implementation in C++. 我想在C ++中提出类似Lisp的cons列表实现。 I will give you my attempt first. 我会先给你我的尝试。

template <typename E1, typename E2>
struct pair {

    constexpr pair() :first{E1{}}, second{E2{}}, empty{true} 
    {}

    constexpr pair(E1 const &f, E2 const &s)
    :first{f}, second{s}, empty{false} 
    {}

    E1 first;
    E2 second;
    bool empty;
};

template <typename Head, typename Tail>
struct cons{
    constexpr cons() :_cons{pair<Head, Tail>{}}
    {}

    constexpr cons(Head h, Tail t) :_cons{pair<Head, Tail>{h, t}}
    {}

    template <typename... Args>
    constexpr cons(Args... args)
    {
        // I want cons(1, 2, 3) to expand into below call
        // in main and cons(1, 2, 3, 4) to expand into
        // cons{1, cons{2, cons{3, cons{4, cons<int, int>()}}}}
    }

    constexpr auto car() {
        return _cons.first;
    }

    constexpr auto cdr() {
        return _cons.second;
    }

    pair<Head, Tail> _cons;
};

int main(){
    constexpr cons c{1, cons{2, cons{3, cons<int, int>{}}}};
}

Basically, I want to fill in the part below that implements a variadic constructor. 基本上,我想填写下面实现可变参数构造函数的部分。

template <typename... Args>
constexpr cons(Args... args)
{
    // I want cons(1, 2, 3) to expand into below call
    // in main and cons(1, 2, 3, 4) to expand into
    // cons{1, cons{2, cons{3, cons{4, cons<int, int>()}}}}
}

I don't know how can I process parameter packs in such a recursive way. 我不知道如何以这种递归方式处理参数包。 If an initializer_list solution is better, I can look at that as well. 如果initializer_list解决方案更好,我也可以看一下。 In the end, I want to be able to do: 最后,我希望能够做到:

cons c1 = {1, 2, 3, 4};

and it will become 它会成为

cons{1, cons{2, cons{3, cons{4, cons<int, int>()}}}};

Here is my solution I came up with after asking the question, it is in the make_list function. 这是我在提出问题后提出的解决方案,它位于make_list函数中。

My Solution 我的解决方案

Not being familiar with Lisp, I went by your description as much as I could. 不熟悉Lisp,我尽可能多地按照你的描述。 You write helper functions make_cons and delegate the construction to the move constructor 您编写辅助函数make_cons并将构造委托给移动构造函数

template<typename, typename>
struct cons;

template<typename U>
auto make_cons(U u)
{
    return cons<U, cons<U, U>>{u, {}};
}

template<typename U, typename... Args>
auto make_cons(U u, Args... args)
{
    return cons<U, decltype(make_cons(args...))>{u, make_cons(args...)};
}

template<typename H, typename T>
struct cons
{
    constexpr cons() :_cons{pair<H, T>{}} {}
    constexpr cons(H h, T t) :_cons{pair<H, T>{h, t}} {}
    template<typename... Args>
    constexpr cons(Args... args) : cons(make_cons(args...)) {}

    pair<H, T> _cons;
};

template<typename U, typename... Args>
cons(U, Args...) -> cons<U, decltype(make_cons(std::declval<Args>()...))>;

template<typename U>
cons(U) -> cons<U, cons<U, U>>;

By your example, I'm assuming you want to have as the last term be cons<U, U> where U is the same type as the terms coming before. 通过你的例子,我假设你想要最后一个术语是cons<U, U> ,其中U与之前的术语类型相同。 You use it as 你用它作为

auto c1 = make_cons(1, 2, 3, 4);
cons c2 = {1, 2, 3, 4};
print<decltype(c1)> p1;
print<decltype(c2)> p2;

Live (read the error message to see the resulting type) 直播(阅读错误消息以查看结果类型)

@PasserBy 's answer inspired me to do that: @PasserBy的回答激励我这样做:

////
template <typename Head, typename Tail>
struct cons{
    constexpr cons() :_cons{pair<Head, Tail>{}}
        {}

    constexpr cons(Head h, Tail t) :_cons{pair<Head, Tail>{h, t}}
        {}

    constexpr auto car() const {
        return _cons.first;
    }

    constexpr auto cdr() const {
        return _cons.second;
    }

    pair<Head, Tail> _cons;
};
////
template <typename Head, typename Tail>
constexpr cons<Head, Tail> makeCons(Head h, Tail t) {
    return cons<Head, Tail>(h, t);
}

template <typename Head, typename... Args>
constexpr auto makeCons(Head h, Args... args) {
    return makeCons(h, makeCons(args...));
}
////

The first part is exactly yours, but with removal of your varadic template constructor. 第一部分完全属于你,但删除了你的varadic模板构造函数。 Also made the getters const, to be able to be used in calling code (calling a constexpr with a non-const method is a compile-time error). 还使得getters const能够用于调用代码(使用非const方法调用constexpr是编译时错误)。

The second part is TWO template functions, the second is the varadic version, where it takes a head, and varadic template to recursively pass it to another version to itself, the first is the specialization of the template function where it accepts only two arguments. 第二部分是TWO模板函数,第二部分是varadic版本,它采用头部,varadic模板以递归方式将其传递给另一个版本自身,第一部分是模板函数的特化,它只接受两个参数。

So the template function will keep resolving to itself (with decreasing one argument), till it is only two, where it will be resolved to the creation of a cons with a pair. 因此模板函数将继续自行解析(减少一个参数),直到它只有两个,在那里它将被解析为创建带有一对的cons

Then call it from your code like this: 然后从你的代码中调用它,如下所示:

constexpr auto c = makeCons(1, 2, 3, 4);
std::cout << c.car();
std::cout << c.cdr().car();
std::cout << c.cdr().cdr().car();

makeCons(1, 2, 3, 4) will recurively resolve to makeCons(1, makeCons(2, 3, 4)) , which will resolve to makeCons(1, makeCons(2, makeCons(3, 4))) makeCons(1, 2, 3, 4)将反复解析为makeCons(1, makeCons(2, 3, 4)) ,这将解析为makeCons(1, makeCons(2, makeCons(3, 4)))

makeCons(3, 4) is the specialized version makeCons(3, 4)是专门版本

First, you only need non-empty packs (no-args call is already handled earlier). 首先,您只需要非空包(之前已经处理过no-args调用)。 Should be straightforward: 应该是直截了当的:

template<typename... Tl> cons(Head h, Tl &&...tl)
    : cons_(std::move(h), Tail(std::forward<Tl>(tl)...)) {}

Btw, don't start identifiers with underscore. 顺便说一句,不要用下划线开始标识符。


Every answer here has one notable flaw: the structure constructed is an improper list: the last cdr in the sequence is not an empty list but rather a sequence. 这里的每个答案都有一个值得注意的缺陷:构造的结构是一个不正确的列表:序列中的最后一个cdr不是空列表而是序列。 To construct proper lists, you'd have to end them properly. 要构建正确的列表,您必须正确地结束它们。 Something like: 就像是:

struct Nil {};
inline bool operator==(Nil, Nil) { return true; }
inline bool operator!=(Nil, Nil) { return false; }
inline Nil make_cons() { return Nil{}; }

template<typename Car> inline auto make_cons(Car &&h) {
     return cons<std::remove_reference_t<Car>, Nil>{std::forward<Car>(h), Nil{}};
}

template<typename Car, typename Cadr, typename... Tail>
inline auto make_cons(Car &&first, Cadr &&second, Tail &&...rest) {
     return make_cons(std::forward<Car>(first)
                      , make_cons(std::forward<Cadr>(second), std::forward<Tail>(rest)...));
}

(For those unfamiliar with Lisp, Alexandrescu's type lists, that were probably one of the inspirations for C++11's variadic patterns, are basically the same.) (对于那些不熟悉Lisp的人,Alexandrescu的类型列表,可能是C ++ 11的可变参数模式的灵感之一,基本相同。)

If you want to avoid recursions in instantiation, you might do something like that: 如果你想避免实例化中的递归,你可能会做类似的事情:

template <typename T>
struct wrap_cons
{
    T&& t;
};


template <typename T, typename Head, typename Tail>
constexpr auto operator +(wrap_cons<T>&& w, cons<Head, Tail> c)
{
    return cons<T, cons<Head, Tail>>{std::forward<T>(w.t), std::move(c)};
}

template <typename T, typename U>
constexpr auto operator +(wrap_cons<T>&& w, wrap_cons<U>&& c)
{
    return cons<T, U>{std::forward<T>(w.t), std::forward<U>(c.t)};
}

template<typename... Args>
constexpr auto make_cons(Args... args)
{
    return (wrap_cons<Args>{std::forward<Args>(args)} + ...);
}

Demo 演示

It might help to have bigger list. 有更大的名单可能会有所帮助。

Btw, std::tuple seems more appropriated that the old type-list. 顺便说一句, std::tuple似乎更适合旧的类型列表。

Every answer here has one serious flaw: the structure constructed is an improper list: the last cdr in the sequence is not an empty list but rather a sequence. 这里的每个答案都有一个严重的缺陷:构造的结构是一个不正确的列表:序列中的最后一个cdr不是空列表而是序列。 To construct proper lists, you'd have to end them properly. 要构建正确的列表,您必须正确地结束它们。 Something like: 就像是:

struct Nil {};
inline bool operator==(Nil, Nil) { return true; }
inline bool operator!=(Nil, Nil) { return false; }
inline Nil make_cons() { return Nil{}; }

template<typename Car> inline cons<std::remove_reference_t<Car>, Nil> make_cons(Car &&h) {
     return cons<Car, Nil>{std::forward<Car>(h), Nil{}};
}
template<typename Car, typename Cadr, typename... Tail> inline auto make_cons(Car &&first, Cadr &&second, Tail &&...rest) {
     return make_cons(std::forward<Car>(first)
                      , make_cons(std::forward<Cadr>(second), std::forward<Tail>(rest)...));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM