簡體   English   中英

在C ++中遞歸地使用參數包

[英]Consuming parameter packs recursively in C++

我想在C ++中提出類似Lisp的cons列表實現。 我會先給你我的嘗試。

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

基本上,我想填寫下面實現可變參數構造函數的部分。

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>()}}}}
}

我不知道如何以這種遞歸方式處理參數包。 如果initializer_list解決方案更好,我也可以看一下。 最后,我希望能夠做到:

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

它會成為

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

這是我在提出問題后提出的解決方案,它位於make_list函數中。

我的解決方案

不熟悉Lisp,我盡可能多地按照你的描述。 您編寫輔助函數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>>;

通過你的例子,我假設你想要最后一個術語是cons<U, U> ,其中U與之前的術語類型相同。 你用它作為

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

直播(閱讀錯誤消息以查看結果類型)

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

第一部分完全屬於你,但刪除了你的varadic模板構造函數。 還使得getters const能夠用於調用代碼(使用非const方法調用constexpr是編譯時錯誤)。

第二部分是TWO模板函數,第二部分是varadic版本,它采用頭部,varadic模板以遞歸方式將其傳遞給另一個版本自身,第一部分是模板函數的特化,它只接受兩個參數。

因此模板函數將繼續自行解析(減少一個參數),直到它只有兩個,在那里它將被解析為創建帶有一對的cons

然后從你的代碼中調用它,如下所示:

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)將反復解析為makeCons(1, makeCons(2, 3, 4)) ,這將解析為makeCons(1, makeCons(2, makeCons(3, 4)))

makeCons(3, 4)是專門版本

首先,您只需要非空包(之前已經處理過no-args調用)。 應該是直截了當的:

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

順便說一句,不要用下划線開始標識符。


這里的每個答案都有一個值得注意的缺陷:構造的結構是一個不正確的列表:序列中的最后一個cdr不是空列表而是序列。 要構建正確的列表,您必須正確地結束它們。 就像是:

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

(對於那些不熟悉Lisp的人,Alexandrescu的類型列表,可能是C ++ 11的可變參數模式的靈感之一,基本相同。)

如果你想避免實例化中的遞歸,你可能會做類似的事情:

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

演示

有更大的名單可能會有所幫助。

順便說一句, std::tuple似乎更適合舊的類型列表。

這里的每個答案都有一個嚴重的缺陷:構造的結構是一個不正確的列表:序列中的最后一個cdr不是空列表而是序列。 要構建正確的列表,您必須正確地結束它們。 就像是:

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