Soumyajit Roy
Soumyajit Roy

Reputation: 463

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

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());
}

Upvotes: 4

Views: 2266

Answers (3)

Nick
Nick

Reputation: 10539

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

Upvotes: 3

test failed in 1.08s
test failed in 1.08s

Reputation: 601

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.

Upvotes: 11

Hikmat Farhat
Hikmat Farhat

Reputation: 592

I am not sure which compiler you are using but your code in g++ gives a segmentation fault which it should since when the lambda is called oneStr doesn't exist. If you reverse the order it should work but note that the line str.oneStr=std::string("one") would replace the "two" with one. Maybe you want str.oneStr=str.oneStr+std::string("one")

Upvotes: 1

Related Questions