Search code examples
c++pointersconstructorconstantsctor-initializer

How to perform complex initialization of a const data member in the member initializer list


Is there a way in modern C++ to initialize a const value in a class derived from accessing a pointer argument? and that pointer might be null, so basically after doing a check on the pointer?

e.g.

// Dummy class to pass as argument in Class A through a pointer, just for this given example
class Zero
{
public:
   int i = 0;
};

class A
{
public:
 A(Zero* z) : isZero(z->i == 0) // in this simple e.g.
// could be: isZero(z == nullptr ? false : z->i), but immagine having a more complex structure instead,
// this kind of verbose way will work,
// but i am asking if something is already designed to deal with this kind of situation
 {
    // But here i can check the eventual nullpointer (or empty reference, or whatever other smart pointers
    if (z==nullptr)
       isZero=false; // or true or throw or whatever... but anyway can't be assigned here

 }

 const bool isZero;
};

Note:

If not a pointer could be a reference or a smart pointer but semantically and grammatically i consider equivalent and i think it is not possible to express this intend in C++ (yet at least)

Note 2:

if missed to read the note another workaround could be a kind of z==nullptr ? false : z->i in the initializer list, but it still feels hacky and complex scenario based on this kind of pattern will be very verbose and potentially error prone rather then just check if z==nullptr once, just specifically asking if there is something to deal with it more smoothly

P.S.

I am not asking to remove the const keyword as it is a workaround, or whatever it would make it working. I am exactly asking if there is a way to cover the e.g. scenario above in C++ or if it is even considered rather than find workarounds like removing the const or doing the checks for each const variable in the initializer list. or even storing the pointer of the argument and then check it every single time, or even pass the const bool as argument, all could work but really i am asking if there is something for this kind of scenario and eventually if not why


Solution

  • As you've said, in the case of A(Zero* z) : isZero(z->i == 0) the initialization of isZero is so simple that it can be a single expression. If if it is not so simple, you can use an IILE or simply a private member function.

    Immediately invoked lambda expression (IILE)

    This C++11 solution does not require you to define any new symbols, so it's arguably the best and most minimal approach. However, some people may not like it due to readability.

    A(Zero* z) : isZero([z] {
        // lots and lots of complicated decision making ...
        return true; // or return something else
    }()) /* empty constructor body: */ {}
    

    Private member function

    Alternatively, you can define a (static) member function which contains all the complicated logic.

    private:
      static bool initIsZero(Zero* z);
    public:
      A(Zero* z) : isZero(initIsZero(z)) {}
    

    It is also possible to call non-static member functions in the member initializer list, however, this is very dangerous because not every subobject has been initialized yet. You would have to be very careful not to call a member function in that context which depends on not-yet-initialized members. See Is there any problem with calling functions in the member initializer list?

    Notes on const members and late-initialization in general

    You have said that you don't want to remove const as a workaround. While that makes sense, you should carefully consider whether you really want const members, since they make the containing class non-assignable.

    Furthermore, one of the above solutions should be used regardless of whether a data member is const or not. In the constructor body, initialization does not take place; only assignment does. For trivial types, the distinction doesn't really matter unless they are const, but it's generally good practice not to "late-initialize" through assignment no matter what. See CppCoreGuidelines C.49: Prefer initialization to assignment in constructors