我试图使用constexpr
在C ++中模拟函数式编程语言中的pair
列表结构。我有一个template <typename E1, typename E2>
struct pair {
constexpr pair()
:_car{E1{}}, _cdr{E2{}}
{}
constexpr pair(const E1 &car, const E2 &cdr)
:_car{car}, _cdr{cdr}
{}
constexpr auto car() const{
return _car;
}
constexpr auto cdr() const{
return _cdr;
}
friend std::ostream& operator<<(std::ostream& str,
pair<E1, E2> p){
if(p == pair{})
return str;
str << p.car() << " " << p.cdr();
return str;
}
template <typename Functor>
friend constexpr auto fmap(Functor f,
const pair<E1, E2> p){
if constexpr (std::is_fundamental<E1>::value &&
std::is_fundamental<E2>::value)
return pair{f(p.car()), f(p.cdr())};
else if(std::is_fundamental<E1>::value &&
!std::is_fundamental<E2>::value)
return pair{f(p.car()), fmap(f, p.cdr())};
}
const E1 _car;
const E2 _cdr;
};
template <typename E1, typename E2>
constexpr bool operator==(const pair<E1, E2>& p1, const pair<E1, E2>& p2)
{
return (p1.car() == p2.car()) && (p1.cdr() == p2.cdr());
}
类型。这是两个不同的东西的持有者,但也支持嵌套对。这是代码。
nested_pair
作为此类型的包装,我有一个nested_pair
类型。这使我更容易使用template <typename Head, typename Tail>
class nested_pair{
public:
constexpr nested_pair():p{}
{}
constexpr nested_pair(Head h, Tail t)
:p{h, t}
{}
constexpr auto prepend(Head h) const{
return nested_pair<Head, decltype(p)>{h, p};
}
constexpr auto head() const {
return p.car();
}
constexpr auto tail() const {
return nested_pair<decltype(p.cdr().car()),
decltype(p.cdr().cdr())>
{p.cdr().car(),
p.cdr().cdr()
};
}
constexpr bool is_empty() const {
return p == pair<decltype(p.car()),
decltype(p.cdr())>
{};
}
template <typename Functor>
friend constexpr auto fmap(Functor f, const nested_pair l) {
const auto res = fmap(f, l.p);
return nested_pair{res.car(), res.cdr()};
}
friend std::ostream& operator<<(std::ostream& str,
nested_pair<Head, Tail> l){
str << l.p;
str << "\n";
return str;
}
private:
const pair<Head, Tail> p;
};
的.aka列表。实际列表只是这个包装器的一个typedef。这是代码。
nested_pair
我的pair
只允许前置,因为如果将列表存储为一对头尾,则追加需要O(n)递归调用。在这里,我将大量工作委托给包含nested_pair
的{{1}}和pair
构造函数。我相信这些工作很好。我使用以下变量模板将列表定义为嵌套对。
template <typename T>
using list = nested_pair<T, T>;
现在,问题是,我想使用此list
来制作string
类型,就像在list<char>
中一样。这应该是所有constexpr,尽我们所能。我有另一个版本用const char []
来构建constexpr字符串,但现在我想使用结构递归。这是我失败的尝试。
class lstring{
public:
template <std::size_t N>
constexpr lstring(const char(&cont)[N]) :size{N} {
size_t ind = N - 1;
while(ind >= 0){
content = content.prepend(cont[ind]);
ind--;
}
}
private:
const size_t size;
const list<char> content;
};
当然这不起作用。 constexpr构造函数经历了一个while循环并违反了constexpr的规则,我相信我们不能在constexpr函数中循环。这也不利用列表的递归结构。如何以这种方式构造字符串?我应该使用带char... args
的可变参数模板吗?我怎样才能在结构上拆开它?我希望能够从像list<char> s{"hello world"}
这样的字符串文字中初始化它。
答案 0 :(得分:1)
你有一个概念性的问题:
您的lstring
包含的list<char>
实际上是nested_pair<char, char>
,而pair<char, char>
又包含char
。 您的字符串始终包含两个list<char, 5>
s。
字符串类和列表类都需要将其长度编码为其类型的一部分。即您需要char
获取5 pair<char, pair<char, pair<char, pair<char, char>>>>
的列表(因此包含nil
)。否则,您需要动态内存 - 这对于编译时常量代码来说是一个明确的 no 。
现在,进行演示。我希望它能给你一些关于如何实现某些事情的想法。对我来说也很有趣;)与你的设计选择相反,我会使用一个特殊的哨兵值 - namespace list
- 来标记列表的结尾。以下所有代码均位于 struct nil {
template<typename U>
constexpr auto prepend(U && u) const;
};
:
nil
prepend(mylist, element)
(空列表)有一个成员函数模板,用于向前面添加内容。它只是声明 - 未定义 - 来打破循环依赖。
注意:这里是否使用会员功能或免费功能是个人品味/风格的问题。通常我会使用免费功能(mylist.prepend(element)
),但我想反映您的预期用途( namespace implementation {
template<typename T>
using no_ref_cv = std::remove_cv_t<std::remove_reference_t<T>>;
}
template<typename Car, typename Cdr>
struct cons {
Car car;
Cdr cdr;
template<typename U>
constexpr auto prepend(U && u) const {
using implementation::no_ref_cv;
return cons<no_ref_cv<U>, cons<Car, Cdr>>{std::forward<U>(u), *this};
}
};
)。
接下来是最重要的结构 - &#34;对&#34; - 建立列表。以Lisp的cons细胞命名:
prepend
它只是一对简单的。 const
创建了一个新的缺点,新元素作为它的第一个元素和当前缺点的副本(它的副本)。我删除了volatile
和cons<char, cons<char, cons<const char, cons<char, nil>>>>
,因为它有点令人头疼(尝试找出为什么cons<char, cons<const char, cons<char, cons<char, nil>>>>
无法转换为nil::prepend
)
尽管如此, template<typename U>
constexpr auto nil::prepend(U && u) const {
using implementation::no_ref_cv;
return cons<no_ref_cv<U>, nil>{std::forward<U>(u), *this};
}
的实施基本相同:
template<typename Car, typename Cdr>
constexpr auto make_cons(Car && car, Cdr && cdr) {
using implementation::no_ref_cv;
return cons<no_ref_cv<Car>, no_ref_cv<Cdr>>{
std::forward<Car>(car), std::forward<Cdr>(cdr)};
}
此外,我喜欢免费功能,以及#34; make&#34;事情,所以:
list<char> s{"hello world"}
现在,问你的问题:
如何从结构上解压缩?我希望能够从像
list<char>
这样的字符串文字中初始化它。
auto s = list::make_list("hello world")
不可能(记住 - 你也需要那个长度!)。但是CharT (&array)[N]
。
您已经有代码来获取字符串文字的长度(参数类型N
),使用cons
您可以构建一个具有足够嵌套 namespace implementation {
template<typename T, std::size_t N>
struct build_homo_cons_chain {
using type = cons<T, typename build_homo_cons_chain<T, N - 1u>::type>;
};
template<typename T>
struct build_homo_cons_chain<T, 0u> {
using type = nil;
};
}
的类型来保存您的列表:
N == 0
nil
只是cons
(空列表),其他所有内容都是N - 1
,其中包含元素和长度为car
的列表。这允许您为列表定义正确的类型,您可以使用它来默认初始化它的实例,然后遍历using list_t = typename implementation::build_homo_cons_chain<char, N>::type;
list_t my_new_list;
// fill my_new_list.car, my_new_list.cdr.car, ... probably with recursion
成员来填充它。像这样:
char
这种方法的问题在于,您需要列表的元素类型既可以是默认可构造的,也可以是可分配的。问题不是 namespace implementation {
template<std::size_t O, std::size_t C>
struct offset_homo_builder {
template<typename T, std::size_t N>
constexpr auto from( T (&array)[N]) {
return offset_homo_builder<O - 1u, C - 1u>{}.from(array).prepend(array[N - O]);
}
};
template<std::size_t O>
struct offset_homo_builder<O, 0u> {
template<typename T, std::size_t N>
constexpr auto from( T (&array)[N]) {
return nil{};
}
};
}
,但这些都是严格的要求,所以当我们从数组中提供的元素(字符串文字)中复制/移动构造列表元素时,我们会更好:< / p>
O
C
是相对于数组的 end 的偏移量,from
我们仍需要构建列表的缺点。 N
成员函数模板采用长度为N - O
的数组,并将implementation::offset_homo_builder<3,2>::from("ab")
处的数组中的元素预先添加到它以递归方式构建的(较短)列表中。
示例:offset_homo_builder<3,2>::from("ab") --> N = 3, O = 3, C = 2
: cons{'b', nil}.prepend('a') => cons{'a', cons{'b', nil}}
^
|--- offset_homo_builder<2, 1>::from("ab") --> N = 3, O = 2, C = 1
: nil.prepend('b') => cons{'b', nil}
^
|--- offset_homo_builder<1, 0>::from("ab") --> N = 3, O = 1, C = 0 (!specialisation!)
: nil
C
'\0'
计数对于忽略字符串文字末尾的 template<typename T, std::size_t N>
constexpr auto make_homogenous(T (&array)[N]) {
return implementation::offset_homo_builder<N, N>{}.from(array);
}
非常重要。所以现在你可以创建一个包含数组所有元素的列表:
template<std::size_t N, typename CharT, typename = typename std::char_traits<CharT>::char_type>
constexpr auto make_string(CharT (& array)[N]) {
static_assert(N > 0, "assuming zero terminated char array!");
return implementation::offset_homo_builder<N, N - 1>{}.from(array);
}
或者构建一个字符串,其中遗漏了最后一个元素:
nil
最后,要使用此列表,您不需要查看元素的类型。请停在 template<typename F, typename Car, typename Cdr>
constexpr auto fmap(F functor, cons<Car,Cdr> const & cell) {
return make_cons(functor(cell.car), fmap(functor, cell.cdr));
}
template<typename F>
constexpr auto fmap(F functor, nil const &) {
return nil{};
}
:
foldl
foldr
,operator<<
和朋友可以类似地实施。您可以使用foldl
实现namespace list
。
结束constexpr
。
另外,检查我们是否仍然constexpr char inc(char c) {
return c + 1;
}
static_assert(fmap(inc, list::make_string("ab").prepend('x')).car == 'y', "");
:
fmap
请注意argument dependent lookup(ADL)的美丽......我可以说list::fmap
代替void dispatch(const std::function<void(void)>& work, Priority priority = Priority::Default, bool loop = false);
。适用于通用代码。