Brain collapsing rules in C++

Consider the declaration of a class that contains a lambda function or a reference to it:

#include <utility>

template <class Func>
struct Holder
{
    Holder(Func && func) : m_func(std::forward<Func>(func))
    {
    }

    Func m_func;
};

template <class Func>
auto MakeHolder(Func && func)
{
    return Holder<Func>(std::forward<Func>(func));
}

In the code below h1 is equivalent to h2:

int main()
{
    auto func = [](int val) { };

    Holder h1 = MakeHolder(func);

    Holder<decltype(func) &> h2(func);

    return 0;
}

and the following code would not compile:

int main()
{
    auto func = [](int val) { };

    Holder<decltype(func)> h2(func);

    return 0;
}

it is because func is of type const Lambda & but the constructor parameter is of type Lambda &&.

Furthermore, the following code will crash:

auto MakeCorruptedHolder()
{
    const std::shared_ptr<std::string> p = std::make_shared<std::string>("abc");

    auto func = [p]()
    {
        std::cout << "Labda with " << *p << std::endl;
    };

    return MakeHolder(func);
}

int main()
{
    Holder h = MakeCorruptedHolder();

    h.m_func();

    return 0;
}

But if we replace line 10 with

return MakeHolder(std::move(func));

it stops to crash, because now Func is the value, but not the reference.

To prevent your brain from a collapse it is better to define Holder class like this:

template <class Func>
struct Holder
{
    Holder(Func func) : m_func(std::move(func))
    {
    }

    std::decay_t<Func> m_func;
};

thus it will contain the value, whatever you do with it.

Leave a Reply

Your email address will not be published.