1

Following code does not initialize struct string members to same value.

#include <string>
#include <iostream>

struct S
{
    std::string s1;
    std::string s2;
    S(std::string const& s) : s1{s}{}
    S(int i) : S([&]{
        s2 = std::to_string(i);
        return std::to_string(i);
    }())
    {}
};

int main()
{
    S s{123};
    std::cout << s.s1 << "|" << s.s2;

    return 0;
}

I'm getting segmentation fault in gcc (tried different versions), and 123| in clang (different versions also) through Wandbox.

I'm getting a read access violation Visual Studio 15.9.16

Thank you.

user1832484
  • 371
  • 3
  • 8

2 Answers2

6

The argument you give to the constructor (i.e. the lambda) cannot access internal members, because they have not been constructed yet.

This should work:

struct S
{
    std::string s1;
    std::string s2;
    S(std::string const& s) : s1{s}{}
    S(int i)
      : S(std::to_string(i))
    {
        s2 = s1;
    }
};
villintehaspam
  • 8,540
  • 6
  • 45
  • 76
  • 1
    More generally this means that when evaluating argument for any ctor, the class member "does non exist yet"? – user1832484 Sep 20 '19 at 09:18
  • @user1832484 you are trying to use it as parameter to the constructor, ie even before the constructor is called – 463035818_is_not_an_ai Sep 20 '19 at 09:19
  • @user1832484 no, they simply weren't constructed before you call another constructor of this class, so yes, whole object wasn't created yet.You cannot change order of creation, even if you write that section in wrong order. `s2` always initialized after `s1`, both done after constructor call but before enetering its body. – Swift - Friday Pie Sep 20 '19 at 09:20
  • 1
    @user1832484: The class members are constructed before the opening brace of the constructor. In this case your constructor _delegates_ to another constructor to construct the members, so they do not exist until after that has completed. – villintehaspam Sep 20 '19 at 09:28
1

Inspecting an uninitialized object is an UB, in this case it'd been obscured by this string:

       s2 = std::to_string(i);

It's a call to operator= of std::string for object stored at location of s2. It wasn't initialized at that point yet. I assume that you could fix it by initializing s2, but it's not possible to do before constructor call in initialization list.

You cannot change order of creation, even if you write that section in wrong order. s2 always initialized after s1, both done after constructor call but before entering its body. Thus the order looks like:

  1. Call of ctor S(int) with initialization of argument i;
  2. Creation of lambda object, capturing this and i
  3. Call of operator() of lambda
  4. Call operator= of std::string for object stored at location of s2
  5. Creation of std::string
  6. Delegating call of ctor S(std::string) with argument s with reference to created string.
  7. Initialization of s1 with value of s
  8. Default initialization of s2

Bullets 4. and 8. happen in order which isn't legal, even worse, due to implementation, it may not to yield an error, so gcc version likely writes that data into some random area of memory. MSVC, at least in debug version, may generate some diagnostic code, for standard doesn't define program behavior for such case.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42