在C ++中列出<char>类型的constexpr字符串

时间:2018-04-09 20:43:49

标签: c++ constexpr

我试图使用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"}这样的字符串文字中初始化它。

1 个答案:

答案 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创建了一个新的缺点,新元素作为它的第一个元素和当前缺点的副本(它的副本)。我删除了volatilecons<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

foldroperator<<和朋友可以类似地实施。您可以使用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); 。适用于通用代码。