91

I figured out that it's possible to initialize the member variables with a constructor argument of the same name as show in the example below.

#include <cstdio>
#include <vector>

class Blah {
    std::vector<int> vec;

public:
    Blah(std::vector<int> vec): vec(vec)
    {}

    void printVec() {

        for(unsigned int i=0; i<vec.size(); i++)
            printf("%i ", vec.at(i));

        printf("\n");
    }
};

int main() {

    std::vector<int> myVector(3);

    myVector.at(0) = 1;
    myVector.at(1) = 2;
    myVector.at(2) = 3;

    Blah blah(myVector);

    blah.printVec();

    return 0;
}

g++ 4.4 with the arguments -Wall -Wextra -pedantic gives no warning and works correctly. It also works with clang++. I wonder what the C++ standard says about it? Is it legal and guaranteed to always work?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Nils
  • 13,319
  • 19
  • 86
  • 108
  • 1
    Good question. I've actually been using this "style" quite a lot. Never doubted that it is allowed. – ltjax May 31 '11 at 09:07

4 Answers4

107

I wonder what the C++ standard says about it? Is it legal and guaranteed to always work?

Yes. That is perfectly legal. Fully Standard conformant.

Blah(std::vector<int> vec): vec(vec){}
                             ^   ^                           
                             |   |
                             |    this is the argument to the constructor
                             this is your member data

Since you asked for the reference in the Standard, here it is, with an example.

§12.6.2/7

Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified.

[Example:
class X {
 int a;
 int b;
 int i;
 int j;
 public:
 const int& r;
  X(int i): r(a), b(i), i(i), j(this->i) {}
                      //^^^^ note this (added by Nawaz)
};

initializes X::r to refer to X::a, initializes X::b with the value of the constructor parameter i, initializes X::i with the value of the constructor parameter i, and initializes X::j with the value of X::i; this takes place each time an object of class X is created. ]

[Note: because the mem-initializer are evaluated in the scope of the constructor, the this pointer can be used in the expression-list of a mem-initializer to refer to the object being initialized. ]

As you can see, there're other interesting thing to note in the above example, and the commentary from the Standard itself.


BTW, as side note, why don't you accept the parameter as const reference:

 Blah(const std::vector<int> & vec): vec(vec) {}
      ^^^^const              ^reference

It avoids unneccessary copy of the original vector object.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Thx for the quick answer, however in the standards document (n3290) I can't find initialization lists, which chapter? – Nils May 31 '11 at 08:50
  • So if I accept it as a reference it's just copied once? From the function scope to the member variable. But if I don't accept the parameter as a reference it is copied again just in the scope of the constructor, right? Thx for pointing that out. – Nils May 31 '11 at 08:56
  • @Nils: Yes. Copied from the original source to the final destination. Original source = the vector in the `main()`, and final destination = member data. – Nawaz May 31 '11 at 08:59
  • 3
    In C++0x it's better to accept it by-value, then move it to its destination: `Blah(std::vector vec) : vec(std::move(vec)){}`. You can simulate this in C++03 like this: `Blah(std::vector vecSrc) { std::swap(vec, vecSrc); }`. – GManNickG May 31 '11 at 09:04
  • @GMan: Nice thought. Thanks :-) – Nawaz May 31 '11 at 09:06
  • n3290 is not a standards document. – Lightness Races in Orbit May 31 '11 at 09:15
  • @GMan: Only if you actually want it moved. – Lightness Races in Orbit May 31 '11 at 09:15
  • @Tomalak: Huh? Both my snippets give the same end result as the OP's code. – GManNickG May 31 '11 at 09:21
  • @GMan: Ah yes, you're still accepting by value. Let this be a lesson that this approach is _downright obfuscating_. – Lightness Races in Orbit May 31 '11 at 09:24
  • @Tomalak: lol, I don't think so, it's certainly more efficient in general. It's just a paradigm shift we'll have to make. – GManNickG May 31 '11 at 09:35
  • @GMan: The compiler can't elide the double-copy? – Lightness Races in Orbit May 31 '11 at 09:46
  • @Tomalak: I'm sure it *could*, I don't know if any do. Move-semantics are added so we no longer have to hope for copy-elision, we can explicitly do it, guaranteed. – GManNickG May 31 '11 at 16:10
  • @GMan: There's a trade-off between doing it explicitly, and actually being able to read the code. – Lightness Races in Orbit May 31 '11 at 16:13
  • 3
    @Tomalak: lol, I'm still surprised you find that unreadable. It's pretty straight-forward and, I thought, common-place. – GManNickG May 31 '11 at 16:16
  • 1
    @GMan: I still class it as a "trick". I know what it does, but it's not obvious from the code at a glance what it _means_, I think. You have to think through the steps to realise that the original _original_ object isn't being moved anywhere. – Lightness Races in Orbit May 31 '11 at 16:22
  • 2
    Although it's legal, it's bad practice. It muddies the water when using -Wshadow (or equivalent), and opens you up to more pernicious programming errors. – Rick Mar 18 '14 at 22:29
  • @GManNickG The one pitfall about initializing `vec(std::move(vec))` is, that if one later refers to `vec` in the constructor code, that vector will be empty, as it is the already moved-away from constructor argument, not `this->vec`, which took over the vector's contents. – Kai Petzke Mar 30 '21 at 19:57
  • @KaiPetzke That is an issue with naming, not moving. – GManNickG Mar 31 '21 at 01:10
  • @GManNickG The pitfall is with moving vs. copy initializing: If you do `: vec(vec)`, both ` vec` (constructor argument) and `this->vec` (object element) will be copies of the same value. But after `: vec(std::move(vec))`, the value has been moved to `this->vec` only. – Kai Petzke Mar 31 '21 at 12:13
  • @KaiPetzke Again, that is a naming issue. You have two different values with the same name. Why would you not move here? Is your goal to have one copy of something, make a second copy, then drop the first copy pointlessly? Or is it to have one copy of something and then simply move it to its destination? This is an extremely old answer and not even a debate anymore, you move. If you want to reference the two different values, give them different names. – GManNickG Mar 31 '21 at 17:47
  • @GManNickG There is no need to give two different names, as `this->vec` and `vec` reference both values already. And there is even no need to reference the moved-away-from value, as it will be empty. All, I said is, that after initializing `vec(std::move(vec))`, people should be cautious, that the value has been moved to `this->vec`. In case, they need to access the value in the constructor body, they shouldn't use `vec`, as that one will be empty. – Kai Petzke Apr 01 '21 at 02:56
  • Even if you use different names like `vec(move(argVec))`, and then you access `argVec` in the ctor-body, then you have the same problem. I understand that if you use the same name, then it's more likely to make this mistake. – Nawaz Apr 01 '21 at 03:43
15

It is guaranteed always to work (I use it quite often). The compiler knows that the initializer list is of the form: member(value), and so it knows that the first vec in vec(vec) must be a member. Now on the argument to initialize the member, both members, arguments to the constructor and other symbols can be used, as in any expression that would be present inside the constructor. At this point it applies the regular lookup rules, and the argument vec hides the member vec.

Section 12.6.2 of the standard deals with initialization and it explains the process with paragraph 2 dealing with lookup for the member and paragraph 7 with the lookup of the argument.

Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified. [Example:

class X {
   int a;
   int b;
   int i;
   int j;
public:
   const int& r;
   X(int i): r(a), b(i), i(i), j(this->i) {}
};
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 2
    +1 for the key point here, which everyone else left unsaid: this works because the same lookup rules are applied as would be used in the ctor body, *which* - and this is the key point - means the argument hides/shadows the member, which is why this works (for some definition of the word "work"; any hiding/shadowing is poor style IMHO). – underscore_d Sep 15 '18 at 21:58
4

One additional counter argument or perhaps just something to be aware of is the situation in which move construction is used to intialize the member variable.

If the member variable needs to be used within the body of the constructor then the member variable needs to be explcitly referenced through the this pointer, otherwise the moved variable will be used which is in an undefined state.

template<typename B>
class A {
public:

  A(B&& b): b(std::forward(b)) {
    this->b.test(); // Correct
    b.test(); // Undefined behavior
  }

private:
  B b;
};
Artalizian
  • 96
  • 3
1

As others have already answered: Yes, this is legal. And yes, this is guaranteed by the Standard to work.

And I find it horrible every time I see it, forcing me to pause: "vec(vec)? WTF? Ah yes, vec is a member variable..."

This is one of the reasons why many, including myself, like to use a naming convention which makes it clear that a member variable is a member variable. Conventions I have seen include adding an underscore suffix (vec_) or an m_ prefix (m_vec). Then, the initializer reads: vec_(vec) / m_vec(vec), which is a no-brainer.

user1387866
  • 2,834
  • 3
  • 22
  • 28
  • 3
    It's easier to detect the mistake in :x(y),y(y) than it is to detect the same mistake in :m_x(in_y),m_y(in_y). The least decorated names make less distractions and allow the reader to only focus on the essential, and it results in the end with less odds of bugs. These rules in the standard have not been chosen by accident, they have a purpose. – Dom Sep 15 '20 at 18:38