如何使此函数尾递归?

时间:2019-09-11 18:56:06

标签: c++11 recursion tail-recursion

我正在尝试使此函数尾部递归,以便可以使用它来处理大量事件而不会导致堆栈溢出。我确保将递归调用放在函数的最后一行,但是它仍然使递归调用充满了调用堆栈。

我还需要做其他事情以使其尾部递归吗,还是我的编译器不知道如何优化它?

我应该放弃此功能,而是使用循环吗?

template <class Csi>
void GetEvents(EventHandle handle, vector<int> desiredCodes, vector<EventHandle> &events, Csi &csi)
{
    if (handle == INVALID_HANDLE)
    {
        return;
    }

    int code = csi.GetEventCode(handle);
    bool codeSatisfiesSearch = (find(desiredCodes.begin(), desiredCodes.end(), code) != desiredCodes.end());
    if (codeSatisfiesSearch)
    {
        events.push_back(handle);

        handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
    }
    else
    {
        handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
    }
    return GetEvents(handle, desiredCodes, events, csi);
}

2 个答案:

答案 0 :(得分:7)

从表面上考虑这个问题。

在当前形式下,由于vector<int> desiredCodes是按值传递的,因此该代码不适合TCO。递归调用后需要调用者销毁局部向量,因此尾部调用优化不是一个选择。

当我更改代码以通过const引用传递矢量时,我注意到clang的最新版本确实优化了尾调用:https://gcc.godbolt.org/z/qM7QVv

但是,gcc仍然没有:https://gcc.godbolt.org/z/u7yIvR。我注意到是push_back阻止了gcc的优化-注释掉后,递归调用就消除了。

events.push_back(handle)替换为

时,我能够获得gcc 9.2来优化递归调用
events.resize(events.size() + 1);
events[events.size()  - 1] = handle;

所有这些都表明,C ++中的TCO并不是要依赖的东西,因为它非常脆弱并且取决于不可预测的因素。您偶尔会获得一笔可观的奖金,但您却无法以此为基础进行设计。

如果TCO对您(例如,对我很感兴趣)感兴趣,那么您将拥有更多可预测的语言(例如C)或函数式样式束,这将带来更好的运气。

答案 1 :(得分:0)

如果您使用编译器标志将其打开,则现代编译器可以执行此优化。我将您的代码修改为以下代码,以使其to compile on godbolt.org:

#include <algorithm>
#include <stddef.h>
#include <vector>

class EventHandle {
  public:
    constexpr EventHandle() : dummy(~0U) {}
    constexpr auto operator==(const EventHandle& x) const
    {
       return dummy == x.dummy;
    }

  private:
    unsigned dummy;
};

constexpr EventHandle INVALID_HANDLE = EventHandle();

class Csi {
  public:
    int GetEventCode(EventHandle) const;
    EventHandle FindNextEventEx( EventHandle, const int*, int, size_t ) const;
};

void GetEvents(EventHandle handle, const std::vector<int>& desiredCodes, std::vector<EventHandle>& events, Csi &csi)
{
    if (handle == INVALID_HANDLE)
    {
        return;
    }

    int code = csi.GetEventCode(handle);
    bool codeSatisfiesSearch = (std::find(desiredCodes.begin(), desiredCodes.end(), code) != desiredCodes.end());
    if (codeSatisfiesSearch)
    {
        events.push_back(handle);

        handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
    }
    else
    {
        handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
    }
    return GetEvents(handle, desiredCodes, events, csi);
}

我只做了两个重大更改。我为classEventHandle提供了存根Csi定义,以便编译器完全生成代码。我也将desiredCodesevents更改为引用参数,以便编译器不会复制。 (这改变了语义:该函数现在更改了原始的events对象。您可能希望将其声明为std::vector<EventHandle>&& events,以便更明确地掩盖输入参数。)I also added std:: to STL types and algorithms. < / p>

如果将MSVC 19.22 x64赋予/Ox标志,则它将优化为尾递归。 GCC 9.2 x86_64需要-O2或更高版本。 Clang 8.0.0和ICC 19.0.1都需要-O或更高版本。例如,使用-O,ICC代码清单的第213行会优化为jmp

jmp GetEvents(EventHandle, std::vector<int, std::allocator<int> > const&, std::vector<EventHandle, std::allocator<EventHandle> >&, Csi&) #44.12

没有优化标志,第1724行执行递归非尾调用。

call GetEvents(EventHandle, std::vector<int, std::allocator<int> > const&, std::vector<EventHandle, std::allocator<EventHandle> >&, Csi&) #44.12