C++ Logo

std-proposals

Advanced search

Re: [std-proposals] c++ proposal for Initializer/finalizer, version 0.1

From: Lorand Szollosi <szollosi.lorand_at_[hidden]>
Date: Sun, 19 Jul 2026 13:30:43 +0200
Hi,

It doesn’t have to be a keyword.
Consider offsetof in cstddef: it can neither be implemented in standard C++, yet can be «explained» as a macro and implemented in a compiler-specific way when including the given header. Similarly, cpass<T> cannot be implemented in current standard C++, but can be «explained» as a template - and it fits _correctly_ within the template mechanics, for it’s only the *implementation* where cpass<T> and T behaved differently; in the type system, Liskov substitution principle applies.

For whether it’s complicated: I myself tend to write codes in a way that the complication is hidden in an intermediate layer, yet intuition and actual evaluation brings the same result. Here this I think holds. I’d consider to say that this is as simple as possible.

Thanks,
lorro

> On 19 Jul 2026, at 08:45, Ruud Rietvink <ruud_at_[hidden]> wrote:
>
> C++ moderators really don't like new keywords or constructions that make life easier.
> They make up template constructions to allow the new functionality or existing C++ code to implement it in a a roundabout way.
> That's probably the reason why C++ doesn't have things like:
> - properties (get; set;)
> - static constructors
> Instead we get template additions like concepts that may have benefits but probably doesn't appeal new developers. Or another type of string or map.
> It just gets too complicated to learn it at school, or as a first computer language.
>
> C++ is becoming the new Cobol.
>
>> On 2026-07-19 04:29, Lorand Szollosi wrote:
>> Hi,
>>
>> First thing first: aren’t you ok with named ctor and movable types, because then it’s simpler.
>>
>> If not, even then: (something like this)
>>
>> #include <iostream>
>> #include <utility>
>>
>> template<class Derived>
>> class post_init_crtp {
>> protected:
>> class guard {
>> friend class post_init_crtp;
>>
>> public:
>> guard() = default;
>>
>> guard(const guard&) = delete;
>> guard& operator=(const guard&) = delete;
>>
>> guard(guard&& other) noexcept
>> : object_(std::exchange(other.object_, nullptr))
>> {}
>>
>> ~guard() noexcept
>> {
>> if (object_)
>> Derived::post_init(*object_);
>> }
>>
>> private:
>> Derived* object_ = nullptr;
>> };
>>
>> explicit post_init_crtp(guard& g) noexcept
>> {
>> g.object_ = static_cast<Derived*>(this);
>> }
>> };
>>
>> class X final : private post_init_crtp<X> {
>> friend class post_init_crtp<X>;
>>
>> int value_;
>>
>> static constexpr auto post_init = [](X& self) noexcept {
>> std::cout << self.value_ << '\n';
>> };
>>
>> public:
>> X(guard guard = {})
>> : post_init_crtp(guard)
>> , value_(230)
>> {}
>> };
>>
>> int main()
>> {
>> X x;
>> }
>>
>> This gives you the choice to call or not call the init later (some call that risk and slowdown when the `if' is not automatically replaced by the compiler). It works with non-final and descendants and other things as well.
>>
>> Also, in a purely theoretic case where we extended cpass to ctors (as described in cpass - uneval proposal) and you’re ok with CRTP, I can’t yet find a reason why it couldn’t be part of that syntax, something like:
>>
>> template<class T>
>> struct post_init_crtp {
>> post_init_crtp()
>> {
>> if constexpr (requires(T& object) { object.init(); }) {
>> static_cast<T&>(*this).init();
>> }
>> }
>> };
>>
>> class X : private cpass<post_init_crtp<X>> {
>> public:
>> virtual void init()
>> {
>> std::cout << "X\n";
>> }
>> };
>>
>> class XDerived final : public X {
>> public:
>> void init() override
>> {
>> std::cout << "XDerived\n";
>> }
>> };
>>
>>
>> … because we’re talking about the same thing from different perspectives I think. You do it for ctors; I do it for ordinary functions, with a bit more genericity. E.g. in this version, you can have multiple kinds of aspects.
>>
>> Thanks,
>> -lorro
>>
>>>> On 5 Jul 2026, at 16:22, Ruud Rietvink via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>>
>>> On 2026-07-05 15:01, Marcin Jaczewski wrote:
>>>> niedz., 5 lip 2026 o 14:11 Ruud Rietvink <ruud_at_[hidden]> napisał(a):
>>>>> 1) I don't suppress base class implementations, like constructors and
>>>>> destructors they are all called.
>>>> But I can suppress member initialization if type is trivial:
>>>> ```
>>>> struct Baz
>>>> {
>>>> int _data[10];
>>>> Baz() // no zero init of `_data`
>>>> {
>>>> }
>>>> Baz(int i) : _data{ i } // all data are set
>>>> {
>>>> }
>>>> };
>>>> ```
>>> +class() has nothing to do with variabele initialization.
>>>
>>>>> 2) finalizers are independent of destructors, they are called in a
>>>>> different line, before the destructor line.
>>>>> You don't need one to have the other.
>>>>> 3) Exception handling works the same as with destructors, also
>>>>> independent of each other.
>>>> You missed my point, adding this finizers make type not trivial and I
>>>> do not think its good thing.
>>>> At least you would need update every place in standard where they
>>>> refer to not-trivial destructor and add "or finalizer".
>>>>
>>>> Similar case we have with `virtual` , what is the point of `virtual
>>>> -Foo()` when you have normal `~Foo()`?
>>>>
>>>> Overall signature of the finalizer should be the same or at least
>>>> compatible with one from the destructor.
>>> Since finalizing() is separate from destruction(), they may differ in virtuality.
>>>
>>> -Foo() is independent of whatever ~Foo() does. And classes that use + or - are certainly not trivial.
>>>
>>>
>>>>> 4) Propogation to member objects? I don't understand what you mean.
>>>>>
>>>> ```
>>>> struct Foo
>>>> {
>>>> Bar _member;
>>>> };
>>>> ```
>>>>
>>>> We have both `-Foo()` and `-Bar()`. When and how `-Bar()` is called?
>>>> Its called right before `~Foo()` or right before `~Bar()`? or called
>>>> by `-Foo()`?
>>>> You can think for case `std::optional<Bar>` too where `Bar` is not a
>>>> direct member of optional.
>>> The same order as with destruction:
>>> First -Foo(), which calls -Bar() at the end, then ~Foo() that calls ~Bar() at the end
>>>>>> On 2026-07-05 13:56, Marcin Jaczewski wrote:
>>>>>>> I had somehow similar idea for "finalizers" but its was more "unwind" operators
>>>>>>> that are called before dstructors is called.
>>>>>>>
>>>>>>> Main difference would be that different types of stack "unwinds" would
>>>>>>> have its own functions
>>>>>>> to make difference between exception unwinding and normal scope exit.
>>>>>>> Or even cancellation of coroutines. Something would be useful for db
>>>>>>> transact scope cancellation.
>>>>>>>
>>>>>>>
>>>>>>> One thing I realized in my solution is that even in your case this new
>>>>>>> `+foo()` and `-foo()` operators
>>>>>>> need to have the same exception specification and triviality as the
>>>>>>> constructor or destructor.
>>>>>>> E.g. adding `-foo() noexcept(false) { throw 1; }` would change the
>>>>>>> result of `noexcept(foo{})`.
>>>>>>> This should be a hard error if the destructor does not have `noexcept(false)`.
>>>>>>> This is needed to avoid unexpectedly changing behavior of other code
>>>>>>> than need to know this things.
>>>>>>>
>>>>>>> Another thing to consider is how you control base functions of this
>>>>>>> initializers and finishers functions?
>>>>>>> How to suppress base class implementation?
>>>>>>> How to propagate it to member objects? And who and when do that?
>>>>>>> Is `virtual ` required on finalizers when you have a `virtual` destructor?
>>>>>>>
>>>>>>> niedz., 5 lip 2026 o 13:20 Ruud Rietvink via Std-Proposals
>>>>>>> <std-proposals_at_[hidden]> napisał(a):
>>>>>>>> C++ object initializer and finalizer
>>>>>>>> ====================================
>>>>>>>> Date: 2026-07-05
>>>>>>>> Version: 0.1
>>>>>>>> Author: Ruud Rietvink
>>>>>>>>
>>>>>>>>
>>>>>>>> Problem
>>>>>>>> -------
>>>>>>>>
>>>>>>>> In the constructor and destructor of an object you cannot use virtual
>>>>>>>> methods properly.
>>>>>>>> So calling a virtual method in a base class to get some possible derived
>>>>>>>> value is not possible.
>>>>>>>> During construction and destruction there is no guaranteed time that the
>>>>>>>> object is fully constructed,
>>>>>>>> so that virtuality covers all the layers of the object.
>>>>>>>>
>>>>>>>>
>>>>>>>> Possible (partial) solutions
>>>>>>>> ----------------------------
>>>>>>>>
>>>>>>>> 1) One solution is to call an initialize() method after construction,
>>>>>>>> and a finalize() method directly before destruction.
>>>>>>>> This is tedious and can easily be forgotten (exceptions, end of scopes).
>>>>>>>>
>>>>>>>> 2) The initialize() could be run automatically in a factory, calling
>>>>>>>> initialize() in the factory create() method.
>>>>>>>> You would have to force that the construction only takes place through
>>>>>>>> this factory by a private/friend construction,
>>>>>>>> or by adding a special factory tag to the constructor arguments.
>>>>>>>> This doesn't solve the finalize() method though, nor copy
>>>>>>>> construction/cloning.
>>>>>>>>
>>>>>>>> 3) With a RAII wrapper, you could call the initialize() method in the
>>>>>>>> RAII constructor and the finalize()
>>>>>>>> in the RAII destructor. Together with a special constructor tag you
>>>>>>>> could force the RAII constructor to be used.
>>>>>>>> This can be made generic. This does require more memory (pointer in RAII
>>>>>>>> object).
>>>>>>>> This fails for copy construction/cloning.
>>>>>>>>
>>>>>>>> 4) This could be done with templated inheritance.
>>>>>>>> template <typename T> class LogWrapper : public T
>>>>>>>> {
>>>>>>>> public:
>>>>>>>> template <typename... args>
>>>>>>>> LogWrapper(T&&... args) : T(std::forward<Args>(args)...)
>>>>>>>> {
>>>>>>>> initializeLog();
>>>>>>>> }
>>>>>>>>
>>>>>>>> ~LogWrapper()
>>>>>>>> {
>>>>>>>> finalizeLog();
>>>>>>>> }
>>>>>>>> }
>>>>>>>>
>>>>>>>> Wrapping could be nested for different purposes
>>>>>>>> (LogWrapper<XXXWrapper<Class>> object(....)).
>>>>>>>> This fails when the class has clone() functionality (unless said API
>>>>>>>> uses some standard copyable in the Wrapper).
>>>>>>>>
>>>>>>>>
>>>>>>>> X) For all wrapper solutions the problem is that when you want to start
>>>>>>>> or stop using it, or extend it, it is a lot of
>>>>>>>> coding effort.
>>>>>>>>
>>>>>>>>
>>>>>>>> Proposal
>>>>>>>> --------
>>>>>>>>
>>>>>>>> Proposal is to introduce an initializer method that is automatically
>>>>>>>> called after the construction is complete,
>>>>>>>> and a finalizer method that is automatically called before the
>>>>>>>> destruction of an object.
>>>>>>>> Because the finalizer is called before the destructing, the vtable is
>>>>>>>> still correct and virtual calling will work correctly.
>>>>>>>> Proposed names:
>>>>>>>> class::+class() for initializing
>>>>>>>> class::-class() for finalizing
>>>>>>>>
>>>>>>>> Initializer is also called after copy construction.
>>>>>>>>
>>>>>>>> Example:
>>>>>>>> class LogBase
>>>>>>>> {
>>>>>>>> protected:
>>>>>>>> virtual ~LogBase() = default;
>>>>>>>> ...
>>>>>>>> virtual const char* const logName() = 0; // or c++26-reflection
>>>>>>>>
>>>>>>>> +LogBase() { logStart(logName()); }
>>>>>>>> virtual -LogBase() { logEnd(logName()); }
>>>>>>>> ...
>>>>>>>> };
>>>>>>>>
>>>>>>>>
>>>>>>>> class EngineBase: protected LogBase
>>>>>>>> {
>>>>>>>> protected:
>>>>>>>> ~EngineBase() override = default;
>>>>>>>> ...
>>>>>>>> virtual EngineType engineType() = 0;
>>>>>>>> +EngineBase() { addToEngine(engineType()); } //
>>>>>>>> Optional, otherwise default
>>>>>>>> -EngineBase() override { removeFromEngine } // Optional
>>>>>>>> ...
>>>>>>>> };
>>>>>>>>
>>>>>>>> class Derived: protected EngineBase
>>>>>>>> {
>>>>>>>> protected:
>>>>>>>> ...
>>>>>>>> EngineType engineType() override { return
>>>>>>>> EngineType.Something; }
>>>>>>>> const char* const logName() override { return "Derived Something"; }
>>>>>>>> ...
>>>>>>>> };
>>>>>>>>
>>>>>>>>
>>>>>>>> EngineBase* base = new Derived();
>>>>>>>> ...
>>>>>>>> delete base;
>>>>>>>>
>>>>>>>>
>>>>>>>> // Order:
>>>>>>>>
>>>>>>>> // LogBase()
>>>>>>>> // EngineBase()
>>>>>>>> // Derived()
>>>>>>>> // Object intact.
>>>>>>>> // +LogBase() (same order as constructor chain)
>>>>>>>> // +EngineBase()
>>>>>>>> // +Derived() (if any)
>>>>>>>> // Life...
>>>>>>>> // -Derived() (if any, same order as destructor chain, for all
>>>>>>>> implementations of -class())
>>>>>>>> // -EngineBase()
>>>>>>>> // -LogBase()
>>>>>>>> // Object still intact.
>>>>>>>> // ~Derived()
>>>>>>>> // ~EngineBase()
>>>>>>>> // ~LogBase()
>>>>>>>>
>>>>>>>>
>>>>>>>> Advantages
>>>>>>>> ----------
>>>>>>>>
>>>>>>>> a) Easy to implement for compilers.
>>>>>>>> b) Easy to optimize away for compilers, if not needed.
>>>>>>>> c) No clashing new keywords.
>>>>>>>> d) Completely optional and backwards compatible.
>>>>>>>> e) If not used, not in the way.
>>>>>>>> f) Could start a new paradigm: construct in (), initialize in +(),
>>>>>>>> finalize in -(), destruct in ~().
>>>>>>>> Also a lot of construction is moved to header file, these days.
>>>>>>>> Also substitutes often used init() method when multiple
>>>>>>>> constructors are present.
>>>>>>>> --
>>>>>>>> Std-Proposals mailing list
>>>>>>>> Std-Proposals_at_[hidden]
>>>>>>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>> --
>>> Std-Proposals mailing list
>>> Std-Proposals_at_[hidden]
>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>

Received on 2026-07-19 11:31:23