3

I have a class template Wrap<T> with a recursive member function test(int) that I want to pass to an STL algorithm with a lambda (std::accumulate in the code below).

If I use a default capture list of =, and make my recursive meber function static, all is fine and get the result I want.

However, if I make it a non-static member function, both Visual C++ and gcc 4.7.2 complain about an unitialized this-pointer, unless I qualify my recursive call as this->test().

#include <algorithm>
#include <iostream>
#include <vector>

template<typename T>
struct Wrap 
{
   static int test1(int depth)
   {
      std::vector<int> v = { 0, 1, 2, 3 };
      return depth == 0? 1 : std::accumulate(v.begin(), v.end(), int(0), [=](int sub, int const&) {
         return sub + test1(depth - 1);
      });   
   }

   int test2(int depth)
   {
      std::vector<int> v = { 0, 1, 2, 3 };
      return depth == 0? 1 : std::accumulate(v.begin(), v.end(), int(0), [=](int sub, int const&) {
         return sub + /*this->*/test2(depth - 1);
      });   
   }   
};

int main()
{
   std::cout << Wrap<int>::test1(0) << "\n"; // 1
   std::cout << Wrap<int>::test1(1) << "\n"; // 4
   std::cout << Wrap<int>::test1(2) << "\n"; // 16

   Wrap<int> w;
   std::cout << w.test2(0) << "\n"; // 1
   std::cout << w.test2(1) << "\n"; // 4
   std::cout << w.test2(2) << "\n"; // 16
}

Output on LiveWorkSpace:

source.cpp: In instantiation of 'int Wrap<T>::test2(int) [with T = int]':   
source.cpp:32:26:   required from here 
source.cpp:19:74: error: missing initializer for member 'Wrap<T>::test2(int) [with T = int]::<lambda(int, const int&)>::__this' [-Werror=missing-field-initializers]

Uncommenting the /*this->/* piece, gives the same result as for the static member function.

Why do I need to qualify my recursive call with this->?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • I am trying your example and it seems to compile both with Clang 3.2 and GCC 4.7.2 (even on liveworkspace)... – Andy Prowl Jan 19 '13 at 13:50
  • @AndyProwl it gives a warning and does not compile with -Werror – TemplateRex Jan 19 '13 at 13:52
  • I see. I am now trying on MinGW (GCC 4.7.1) and it does not even see `test1` – Andy Prowl Jan 19 '13 at 13:53
  • 1
    it seems to me this is not related with lambdas, but with name lookup in a template – Andy Prowl Jan 19 '13 at 13:54
  • @AndyProwl you might be right about name lookup, but it is lambda-specific becuase writing `std::accumulate` as an explicit for loop does not give any warnings/errors – TemplateRex Jan 19 '13 at 14:10
  • yes, you are correct. i believe this is a bug. from the text of the warning/error, it seems the lambda closure type does contain a `__this` member which captures the `this` pointer, but that member is not initialized in the closure's constructor – Andy Prowl Jan 19 '13 at 14:15
  • This looks like a compiler bug. With some small changes (adding an include for ``, modifying `std::vector` to not use an initializer list), VS2012 compiles this just fine without the `this`. – Yuushi Jan 19 '13 at 14:19

1 Answers1

3

I believe this is a bug of GCC 4.7.2. The warning says:

missing initializer for member 'Wrap<T>::test2(int) [with T = int]::<lambda(int, const int&)>::__this'

Which means that the compiler recognizes the this pointer is to be captured and the generated closure does contain a pointer for it, but that pointer does not get initialized in the closure's constructor.

This is confirmed by the fact that attempting to access/change a member variable (which is not present in your example, but can be easily added) causes a run-time error. For instance, this shows no output at liveworkspace.org

#include <algorithm>
#include <iostream>
#include <vector>

template<typename T>
struct Wrap 
{
    int test2(int depth)
    {
        m_test++;
        std::vector<int> v = { 0, 1, 2, 3 };
        return depth == 0? 1 : std::accumulate(
             v.begin(), v.end(), int(0), [=](int sub, int const&) {
             return sub + /*this->*/test2(depth - 1);
             });   
    }

    int m_test = 0;
};

int main()
{
    Wrap<int> w;
    std::cout << w.test2(2) << "\n"; // 1
}

This code compiles fine with Clang 3.2 and VS2012, which seems to confirm the presence of a bug.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451