Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of c++ lambda expression to initialize a class member

Tags:

c++

c++11

lambda

I want to initialize one class member (std::string filePath) using lambda expression. Program is compiling fine however there is no output. What is wrong here?

#include <iostream>
#include <string>

class MyString
{
  public:
  std::string filePath = [this] ()
  {
      return oneStr.append(" two");
  }();
    
  std::string oneStr;
};

int main()
{
  MyString str;
  str.oneStr = std::string(" one");
  printf("%s", str.oneStr.c_str());
}
like image 960
Soumyajit Roy Avatar asked Dec 05 '22 08:12

Soumyajit Roy


2 Answers

I compiled your code with clang v10.0.1 on Linux 5.8.6. Your code bumped into an core dump. Here are some logs of valgrind (simplified in order not to be confusing):

==12170== Conditional jump or move depends on uninitialised value(s)
==12170==    at 0x49BEAFE: _M_check_length (basic_string.h:322)
==12170==    by 0x49BEAFE: std::string::append(char const*) (basic_string.h:1238)
==12170==    by 0x10944A: MyString::filePath::{lambda()#1}::operator()[abi:cxx11]() const (test.cc:9)
==12170==    by 0x109390: MyString::MyString() (test.cc:7)
==12170==    by 0x109270: main (test.cc:17)

Obviously there's something wrong with the function append. In fact your issue is that you're using oneStr before its initialization.

According to cppreference:

https://en.cppreference.com/w/cpp/language/constructor

The order of member initializers in the list is irrelevant: the actual order of initialization is as follows:

...

3) Then, non-static data member are initialized in order of declaration in the class definition.

As a result, filePath is initialized before oneStr gets initialized. The lambda is evaluated, with this captured, with this->oneStr not initialized.


Changing the order of declaration would fix this undefined behavior:

#include <iostream>
#include <string>

class MyString
{
  public:
  std::string oneStr;
  std::string filePath = [this] ()
  {
      return oneStr.append(" two");
  }();
};

int main()
{
  MyString str;
  str.oneStr = std::string(" one");
  printf("%s", str.oneStr.c_str());
}

But still you may not get expected result (maybe one two?). You will see only one printed. That's because oneStr is firstly initialized with "", then appended with " two" in MyString::MyString(); but finally assigned with " one" before printing in the main function.

like image 110
test failed in 1.08s Avatar answered Dec 26 '22 07:12

test failed in 1.08s


updated once compiled.

#include <iostream>
#include <string>

struct MyString{
  std::string filePath = [this] () {
      return oneStr.append(" two");
  }();
    
  std::string oneStr;
};

int main(){
  MyString str; // 1 
  str.oneStr = std::string(" one"); // 2
  std::cout << str.oneStr << '\n'; // 3
}

At the line "1" str object is created.

filePath string is initialized first. Lambda capture this, but oneStr is not yet initialized, because is defined as second member.

Lambda is using oneStr, so nobody knows what is the result. Actually program crashes silently.


If you want the example to print " one two", do a constructor and initialize oneStr there.

Here is working example:

#include <iostream>
#include <string>

struct MyString{
    MyString(std::string oneStr) : oneStr(std::move(oneStr)){}

    std::string oneStr;

    std::string filePath = [this] () {
        return oneStr.append(" two");
    }();    
};

int main(){
    MyString str(" one");
    std::cout << str.oneStr << '\n';
}

https://gcc.godbolt.org/z/93xbcr

like image 43
Nick Avatar answered Dec 26 '22 09:12

Nick