Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't a lambda (capturing 'this') in a member function-try-block handler access private data members in VC++ 2013?

Not the same as but possibly related to this question about static initializers.

Here the first two functions compile fine, and the last one doesn't in vc++ but does in clang and gcc:

class A {
protected:
    std::string protected_member = "yay";
public:
    void withNormalBlock();
    void withFunctionBlock();
    void noLambda();
};


void A::withNormalBlock() {
    try {
        throw std::exception();
    } catch (...) {
        [this]() {
            std::cout << protected_member << std::endl;
        }();
    }
}

void A::noLambda() try {
    throw std::exception();
} catch (...) {
    std::cout << protected_member << std::endl;
}

void A::withFunctionBlock() try {
    throw std::exception();
} catch (...) {
    [this]() {
        // this line is the problem:
        std::cout << protected_member << std::endl;
    }();
}
  • in clang (OK)
  • in gcc (OK)
  • in vc++ (error C2248: 'A::protected_member' : cannot access protected member declared in class 'A')
  • vc++ 2015 -- same deal

I can't find anything in the standard to suggest that the handler / catch block of a function-try-block should be exempt from function scope or that the lambda's closure type should change. The code compiles if the access type is changed to all public.

What could the root cause be? Is it a bug, or is it something specific to compiler settings that could be changed?

like image 864
MechEngineer Avatar asked Aug 19 '15 05:08

MechEngineer


2 Answers

Seems that this is a bug and the lambda in the catch scope is generated outside class scope. I tried to prove that with typeids but the Visual Studio lambda names are weirdly mangled and the name itself does not prove anything. However, error codes generated by the following snippet show that the names differ:

#include <iostream>
#include <typeinfo>

class Foo {
private:
public:
    void testLambda()
    try {
        auto tryScope = [this]() {};
        void (*p)() = tryScope;
    }
    catch(...)
    {
        auto catchScope = [this]() {};
        void (*p)() = catchScope;
    }

};

Error output:

(10): error C2440: 'initializing' : cannot convert from 'Foo::testLambda::<lambda_8a3a8afea7359de4568df0e75ead2a56>' to 'void (__cdecl *)(void)' (15): error C2440: 'initializing' : cannot convert from '<lambda_8cbc08e7748553fb5ae4e39184491e92>' to 'void (__cdecl *)(void)'

like image 43
Rudolfs Bundulis Avatar answered Sep 24 '22 16:09

Rudolfs Bundulis


I'd guess it is a compiler bug. It reports the same error in VS2015 as well. Curiously, an attempt to explicitly simulate the functionality of lambda works without any issues in VS2015

class A
{
protected:
  std::string protected_member = "yay";
public:
  void withFunctionBlock();
};

void A::withFunctionBlock() try
{
  throw std::exception();
}
catch (...)
{
  struct Closure { 
    Closure(A *this_) : this_(this_) {}
    void operator ()() const { std::cout << this_->protected_member << std::endl; }
    A *this_;
  };
  Closure(this)();
}

I wonder what VS C++ compiler does differently under the hood...

like image 71
AnT Avatar answered Sep 25 '22 16:09

AnT