Search code examples
javac++c++11constantsfinal

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.)


Solution

  • 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.
    }