Date: Sun, 19 Jul 2026 04:29:35 +0200
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
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 02:30:14
