在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;
live(阅读错误消息以查看结果类型(
@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是一个编译时错误(。
第二部分是两个模板函数,第二部分是变性版本,它占用头,而变色模板则将其递归传递给另一个版本,第一个是模板函数的专业化,仅接受它仅接受两个参数。
因此,模板函数将不断解决自己(一个参数减少(,直到它仅两个,在其中它将解决用一对创建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)
是专业版本
如果要避免实例化递归,则可能会做类似的事情:
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)} + ...);
}
演示
有更大列表可能会有所帮助。
btw,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)...));
}
首先,您只需要非空包(早先已经处理了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的变异模式的灵感之一,基本上是相同的。(
- 打包可变参数模板具有零元素时的递归
- 使用 SFINAE 作为模板参数的编译时递归
- 为什么我的递归可变参数模板无法编译?
- 为什么当函数参数未定义为常量引用时存在无限递归?
- 使用参数推导时如何停止模板递归?
- 使用带有一个参数函数的递归找到数字的平方
- 作用于可变类模板参数的递归函数
- 对可变参数函数的递归调用的链接器错误
- 如何将从第 2 个字符开始的字符串作为函数中的参数传递以进行递归,并约束数据 tiee 是函数中的字符串?
- C++ 引用类型作为递归函数参数
- 如何将参数递归绑定到函数?
- 递归回文检查,不使用向量、大小或其他参数
- n维向量的递归可变参数模板函数
- 没有参数的递归,也没有静态或全局变量
- 循环访问多个模板参数的递归模板函数
- 具有编译问题的简单(递归)可变参数模板"accumulate_for"函数
- 传递参数递归 C++(电话号码的字母组合)
- 仅使用引用参数递归反转字符串
- 使用单个参数递归打印菱形
- 用新的模板参数递归调用模板化函数