How do I capture the results of a recursive function at compile-time?

你。 提交于 2019-12-10 18:23:38

问题


#include <iostream>

template <typename T>
struct node {
    T value;
    node const* prev;

    constexpr node(const T& value, node const* prev = nullptr)
        : value{value}, prev{prev} {}

    constexpr node push_front(const T& value) const {
        return node(value, this);
    }
};

struct Something {
    node<int> n;

    constexpr Something(const int i) : n{node<int>(i)} {}

    constexpr Something(const node<int>& n) : n{n} {}
};

constexpr void print(const Something& s) {
    bool first = true;
    for (const node<int>* i = &s.n; i != nullptr; i = i->prev) {
        if (first) {
            first = false;
        } else {
            std::cout << ", ";
        }
        std::cout << i->value;
    }
}

constexpr Something recursive_case(Something& s, const unsigned int i) {
    Something result(s.n.push_front(i % 10));
    auto j = i / 10;
    return j != 0 ? recursive_case(result, j) : result;
}

constexpr Something base_case(const unsigned int i) {
    Something result(i % 10);
    auto j = i / 10;
    return j != 0 ? recursive_case(result, j) : result;
}

int main() { print(base_case(21)); }

I have a recursive function like the one shown above (base_case and recursive_case). I got the idea for the node object from this link: https://gist.github.com/dabrahams/1457531#file-constexpr_demo-cpp-L66, and I modified it to suit my needs. The problem with what I have above is that I am getting a segmentation fault.

Thanks.

Edit(s):

  1. Sorry for not trying a debugger earlier. Here is the output:

        $ lldb ./ww                                                                                              ~/scratch/ww
    (lldb) target create "./ww"
    Current executable set to './ww' (x86_64).
    (lldb) run
    Process 32909 launched: './ww' (x86_64)
    Process 32909 stopped
    * thread #1: tid = 0x4d4e8e, 0x000000010000109b ww`print(s=0x00007fff5fbfec80) + 91 at ww.cpp:32, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
        frame #0: 0x000000010000109b ww`print(s=0x00007fff5fbfec80) + 91 at ww.cpp:32
       29           } else {
       30               std::cout << ", ";
       31           }
    -> 32           std::cout << i->value;
       33       }
       34   }
       35
    (lldb)
    

    I will try to use new or smart pointers, but from what I have read, they cannot be used in constexpr functions.

  2. I tried using using both new and smart pointers.

    • For new, I get this error:

          ww.cpp:19:15: error: constexpr constructor never produces a constant expression [-Winvalid-constexpr]
              constexpr Something(const int i) : n{new node<int>(i)} {}
                        ^
          ww.cpp:19:42: note: subexpression not valid in a constant expression
              constexpr Something(const int i) : n{new node<int>(i)} {}
                                                   ^
      
    • For unique_ptr, I get this error:

          ww.cpp:26:11: note: non-constexpr constructor 'unique_ptr' cannot be used in a constant expression
          : n{std::unique_ptr<node<int>, deleter<node<int>>>(new node<int>(i))} {}
      
  3. I looked into it some more, and I think it is possible to solve this problem using C++ templates. I just need a way to capture the intermediate results of the recursion e.g. some kind of compile-time list and then reverse the order.


回答1:


It is an interesting question, but as Joachim Pileborg said in comment, executing the program in a debugger would have given you the why of the crash.

First the diagnostic:

  • all is fine when hitting print function: s contains a node with a previous node and that's all (s.prev->prev == nullptr)
  • after the std::cout << i->value; it's broken: s.prev->prev != nullptr when the instruction should not have changed that!

As it breaks when calling a function, it smells like there's a dangling pointer or reference around...

Now the explaination:

In your recursive calls, everything (the Something and the nodes are all allocated as local variables and passed to recursive calls as reference. When calling down, all is fine, everything is allocated in a caller function. But when you return, only the Something and its node are corrects: all following nodes were allocated as local variables in now ended function and (calling result the return value) result.prev is a dangling pointer.

Using a dangling pointer is undefined behaviour and a SIGSEGV is possible.

TL/DR: you are allocating nodes of a linked list as local variables in recursive calls and end with dangling pointers so the undefined behaviour that hopefully caused a crash immediately.

BEWARE: undefined behaviour could work in all your tests and break later in production

Possible fix: as a class cannot contain an instance of itself, you cannot use return by value in a linked list and must use dynamic allocation. So you must implement explicit destructors or use smart pointers.

Above was mainly the explaination for the crash. It can be fixed by using smart pointers but constexpr can no longer be used. If struct node is modified that way:

template <typename T>
struct node {
    T value;
    std::shared_ptr<node const> prev;

    node(const T& value, node const* prev = nullptr)
        : value{value} {
    this->prev = (prev == nullptr)
        ? std::shared_ptr<struct node const>(nullptr)
        : std::make_shared<struct node const>(*prev);
    }

    node push_front(const T& value) const {
        return node(value, this);
    }
};

It can be safely returned by copy, because the shared_ptr ensures that you will never get a dangling pointer. But it is true that the constructor can no longer be a constexpr, so only print remains one...

But it makes sense. As recursive_case recusively builds a linked list, I cannot imagine a way to make that a constexpr. It might be possible by first allocating an array of nodes (since you will need a node per digit here) and then linking those existing nodes. But if they are allocated as local variables in a recursive function you will not be able to avoid the dangling problem, and if they are dynamically allocated, it cannot be a constexpr




回答2:


Adding

constexpr node(const node& i_rhs)
    : value(i_rhs.value)
    , prev(i_rhs.prev == nullptr ? nullptr : new node(*i_rhs.prev))
{}

constexpr node(const node&& i_rhs)
    : value(i_rhs.value)
    , prev(i_rhs.prev)
{}

worked for me.

As far as I understand, the whole scenario is the following:

  1. Program enters base_case and creates first Something on stack.
  2. Program enters recursive_case and creates a second Something on stack, linking it with the first something.
  3. Program leaves recursive_case and a second Something is copied and returned.
  4. Program leaves base_case returning second Something and the first Something is not copied anywhere - it still lives on stack (in unused part of it).
  5. Program enters print - this instruction uses stack memory and first Something (which lives in unused part of stack) is filled by trash values.
  6. Program tries to access first Something and gets seg fault.

The fix solves this issue by performing deep copying of the objects. But this fix leads to memory leak: memory allocated in constructor is never freed. This can be fixed by using smart pointers or explicitly implementing destructor.



来源:https://stackoverflow.com/questions/33252137/how-do-i-capture-the-results-of-a-recursive-function-at-compile-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!