Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to defer initialization of a const variable in C++, like Java's "blank final" feature?

In Java we can declare a blank final variable and initialize it later. The compiler will ensure initialization happens exactly once -- failure to initialize or double-initialization are both compile-time errors. For example:

public int bar() {
   return 66;
}

public void foo() {
    final int x; // declare the variable
    ...
    x = bar(); // initialization only once
}

In Java, the compiler can guarantee that x is definitely not assigned on any code path prior to its first assignment, and can guarantee that it is definitely never assigned a second time on any code path. (See Chapter 16, Definite Assignment, of the Java Language Specification for more information.)

How can we achieve similar behavior in C++? Is it possible to have a variable declared const but defer its initialization? (Without casting away the const specifier.)

like image 422
Buddhika Avatar asked Mar 05 '23 05:03

Buddhika


2 Answers

It's not possible to initialize a const except where it's defined. You have to find a way to know it's value where it's defined. If x's value is difficult to determine, consider using the result of a function like

const int x = calc_x();

or a closure like

const int x = []() { /* code to calculate x's value */ }();

constness is part of the type of an object and an object type's cannot be changed under any circumstance, therefore either x is const and you can't initialize it later or x is not const at all.

It's possible to design a wrapper class that would emulate this, but you would at best get a run-time error.

Note that there may seem to be solution in the form of const_cast but that assumes that the object in question is not actually const. In the case of const int x there is no way to legally change it's value after it's initialization.

like image 113
François Andrieux Avatar answered Mar 22 '23 22:03

François Andrieux


C++ doesn't have a built in feature for this. You can kind of build it for yourself though. You can create a class that holds storage for an object of the type you want and you can overload the assignment operator for that so it can only be called and initialized once. That would look like

template<typename T>
class once
{
private: 
    std::aligned_storage_t<sizeof(T), alignof(T)> data;
    T* ptr = nullptr;
public:
    once() = default;
    ~once()
    {
        if(ptr) // it is initialized so call the destructor
            ptr->~T();
        // optionally you can add
        // throw("a once<T> must be initialized once");
        // this can help to enforce that the object is actually initialized as you'll get a runtime exception in code that does not do so
    }
    template<typename U>
    once& operator =(U&& value)
    {
        if (!ptr) // it is not initialized so call constructor
        {
            ptr = new(&data) T(std::forward<U>(value));
        }
        else
            throw ("can only assign to a once<T> once.");
        return *this;
    }
    operator const T&()
    {
        return *ptr;
    }

};

and then you would use it like

int main()
{
    once<int> foo;
    if (1 < -1)
        foo = 21;
    else
        foo = 42;
    std::cout << foo;
    //foo = 23; // uncomment this to get an exception.
}
like image 33
NathanOliver Avatar answered Mar 23 '23 00:03

NathanOliver