Spot The Bug: C++ Lambdas

on C++, Spot The Bug

In VC++2013, this compiles fine:

auto deleter = [](wchar_t *memory) { /* omitted */ };
std::unique_ptr<wchar_t, decltype(deleter)> var(L"Hello, World.");

In VC++2015, it doesn't. Why?

Well it's pretty clear why from the error message: the std::unique_ptr constructor is going to construct a new instance of the second type argument to use as the deleter for the held pointer, and that type, being a lambda, can't be constructed because it doesn't (or shouldn't, rather) have a visible constructor.

Does this mean that the VC++2013 compiler generates lambda with public constructors? Not exactly.

For example, consider the following function:

#include <iostream>
#include <typeinfo>

template<typename T>
T make_it()
{
    T var = T();
    return var;
}

We would expect the following code to compile just fine under VC++2013 and VC++2015; and we'd be right, this is perfectly legal.

int main()
{
    auto instance = make_it<wchar_t>();
    std::cout << typeid(instance).name() << std::endl;
    return 0;
}

Running this produces the following output:

wchar_t

If VC++2013 was improperly generating constructors for lambda types, we would expect this code to succeed under VC++2013... but it doesn't!

int main()
{
    auto lambda = []() {};

    auto instance = make_it<decltype(lambda)>();
    std::cout << typeid(instance).name() << std::endl;

    return 0;
}

VC++2013 also complains about not being able to construct an instance of a lambda, just as it should!

So why did my code suddenly start breaking under VC++2015 when it compiled and executed just fine under VC++2013?

Because I wasn't making an explicit constructor call; in reality, my deleter type was getting created in a manner that is more accurately represented by this version of my make_it function:

template<typename T>
T make_it()
{
    T var;

    return var;
}

In VC++2013, constructing a lambda on the stack is perfectly acceptable (this is the bug) lambdas appear to be constructible when invoking the default constructor, but if the construction pattern would involve the use of the copy constructor, it's disallowed.

In VC++2015, it looks like they fixed it. Which led to some very confusing compiler errors that took a few minutes to realize what was going on. :)

Mad props to Cody Miller of the C++ team for helping me figure out what was going on here.

comments powered by Disqus