I'm doing some experimenting with y-combinator-like lambda wrapping (although they're not actually strictly-speaking y-combinators, I know), and I've encountered a very odd problem. My code operates exactly as I'd anticipate in a Debug configuration (with Optimizations turned off), but skips large (and important!) bits in Release (with it set to Optimizations (Favor Speed) (/Ox)).
Please note, the insides of the lambda functions are basically irrelevant, they're just to be sure that it can recursion correctly etc.
// 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;
}
What should happen is that the first evaluation of d works fine, sets d.m_selfDestructTrigger, and causes itself to be deleted. And then the second evaluation of d should crash, because d no longer really exists. Which is exactly what happens in the Debug configuration. (Note: As @largest_prime_is_463035818 points out below, it shouldn't crash so much as encounter undefined behaviour.)
But in the Release configuration, as far as I can tell, all of the code in evaluate gets skipped entirely, and the execution jumps straight to the lambda. Obviously, break-points in optimised code are a little suspect, but that appears to be what's happening. I've tried rebuilding the project, but no dice; VS seems pretty adamant about it.
Am I crazy? Is there something I've missed? Or is this an actual bug in VS (or even the compiler)? Any assistance in determining if this is a code issue or a tool issue would be greatly appreciated.
Note: I'm on VS2019 16.8.3, using the /std:c++ latest featureset.
 
     
    