我正在尝试使用类似 y-combinator 的 lambda 包装(尽管我知道它们实际上并不是严格意义上的 y-combinator),但我遇到了一个非常奇怪的问题。我的代码完全按照我在 Debug 配置中的预期运行(关闭了优化),但在 Release 中跳过了大(而且很重要!)位(设置为Optimizations (Favor Speed) (/Ox)
)。
请注意,lambda 函数的内部基本上是不相关的,它们只是为了确保它可以正确递归等。
// main.cpp
#include <iostream>
#include <string>
#define uint unsigned int
// Defines a y-combinator-style thing to do recursive things. Includes a system where the lambda can declare itself to be obsolete.
// Yes, it's hacky and ugly. Don't worry about it, this is all just testing functionality.
template <class F>
class YCombinator {
public:
F m_f; // the lambda will be stored here
bool m_selfDestructing = false; //!< Whether the combinator will self-destruct should its lambda mark itself as no longer useful.
bool m_selfDestructTrigger = false; //!< Whether the combinator's lambda has marked itself as no longer useful.
// a forwarding operator:
template <class... Args>
decltype(auto) evaluate(Args&&... args) {
// Avoid storing return if we can,
if (!m_selfDestructing) {
// Pass itself to m_f, then the arguments.
return m_f(*this, std::forward<Args>(args)...);
}
else {
// Pass itself to m_f, then the arguments.
auto r = m_f(*this, std::forward<Args>(args)...);
// self-destruct if necessary, allowing lamdas to delete themselves if they know they're no longer useful.
if (m_selfDestructTrigger) {
delete this;
}
return r;
}
}
};
template <class F> YCombinator(F, bool sd)->YCombinator<F>;
// Tests some instances.
int main() {
// Most basic test
auto a = YCombinator{
[](auto & self, uint in)->uint{
uint out = in;
for (uint i = 1u; i < in; ++i) {
out += self.evaluate(i);
}
return out;
},
false
};
// Same as a, but checks it works as a pointer.
auto b = new YCombinator{
[](auto & self, uint in)->uint {
uint out = in;
for (uint i = 0u; i < in; ++i) {
out += self.evaluate(i);
}
return out;
},
false
};
// c elided for simplicity
// Checks the self-deletion mechanism
auto d = new YCombinator{
[&a, b](auto & self, uint in)->uint {
std::cout << "Running d(" << in << ") [SD-" << self.m_selfDestructing << "]..." << std::endl;
uint outA = a.evaluate(in);
uint outB = b->evaluate(in);
if (outA == outB)
std::cout << "d(" << in << ") [SD-" << self.m_selfDestructing << "] confirmed both a and b produced the same output of " << outA << "." << std::endl;
self.m_selfDestructTrigger = true;
return outA;
},
true
};
uint resultA = a.evaluate(4u);
std::cout << "Final result: a(4) = " << resultA << "." << std::endl << std::endl;
uint resultB = (*b).evaluate(5u);
std::cout << "Final result: b(5) = " << resultB << "." << std::endl << std::endl;
uint resultD = d->evaluate(2u);
std::cout << "Final result: d(2) = " << resultD << "." << std::endl << std::endl;
resultD = d->evaluate(2u);
std::cout << "Final result: d(2) = " << resultD << "." << std::endl << std::endl;
}
应该发生的是,第一次评估d
工作正常,设置d.m_selfDestructTrigger
并导致自身被删除。然后第二次评估d
应该崩溃,因为d
不再真正存在。这正是调试配置中发生的事情。(注意:正如@largest_prime_is_463035818 在下面指出的那样,它不应该像遇到未定义的行为那样崩溃。)
但据我所知,在 Release 配置中,所有代码都evaluate
被完全跳过,执行直接跳转到 lambda。显然,优化代码中的断点有点可疑,但这似乎就是正在发生的事情。我试过重建项目,但没有骰子;VS 似乎对此非常坚定。
我疯了吗?有什么我错过的吗?或者这是 VS(甚至是编译器)中的实际错误?在确定这是代码问题还是工具问题方面的任何帮助将不胜感激。
注意:我在 VS2019 16.8.3 上,使用/std:c++ latest
功能集。