Binglong's space

Random notes on computer, phone, life, anything

Archive for November, 2024

C++ Type Erasure

Posted by binglongx on November 16, 2024

Excellant videos:

Basic ideas in the examples:

  • Create a class W to wrap the concrete types.
  • W constructor captures concrete type information.
    • W has templated constructor, so the constructor has the knowledge of the concrete type T. This information is used to perform operations that need/depend on T, while still in the W constructor. Approaches to capture the type information:
      • Approach 1: Create an internal base class B, and a class template parameterized by the concrete type but deriving from the base, D<T>:B.
        • B has virtual methods to dictate the oprations;
        • D implements the operations with type information of T.
        • W creates a new D object, e.g., moving from t in W::W(T t). It stores a pointer (e.g. std::unique_ptr<B>) as data member.
          • W operations forward to B operations using the pointer member above.
        • If it is too expensive even moving T, W may create a non-owing reference to t in W::W(T& t), which can be captured in B and/or D.
        • If you need to capture other information to customize W, use extra parameters in W::W(), either as template parameter or function parameter, for compile time or run-time customized behavior. An example is to pass a lambda to customize the operation.
        • If dynamic allocation to create D is too expensive, W can use use small buffer optimization (SBO) to move T over directly if it is a small type. It’s even possible to use a storage policy template parameter to customize W behavior, e.g. to always use dynamic allocation, to always use SBO, or to use SBO with fallback to dynamic allocation.
      • Approach 2: W Constructor can create a lambda L and store it as data member, whose body uses the T information, e.g., calling a function depending on T.
  • After the construction, W no long knows the concrete T type (type erased). It is a plain wrapper class object in appearance, and you cannot ask what’s the concrete it wraps.
  • Operations on the W object rely on the internal B member or L member to perform.
  • D or L‘s operations can connect to T in different ways:
    • External polymorphism: D/L assumes a free function override that takes T object, the affordance function. As long as someone writes such the affordance, W would work with T, otherwise it would have a compile error. Note that even if you are not able to modify T, you can still make use of W by creating the affordance override.
    • D/L assumes T has a specific method to perform an action. This is not always possible if you don’t control T.

A beautiful type erasure can represent the unrelated types with value semantics and have high performance.

Note that type erasure must use indirection (to hide “type”), which has similar performance loss like vtable, compared to class or function template that exposes type or implementation details.

Maybe we can create utitily class template for type erasure supporting:

  • Concrete type object access
    • Referencing (non-owning)
    • Moving
    • Copying
  • Concrete type operation access (e.g. using C++20 concept)
    • External polymorphism / free affordance function override; and/or
    • requiring specific member function signature

Posted in C++ | Tagged: , , , , , , , , , | Leave a Comment »

Type-Punning with Union

Posted by binglongx on November 15, 2024

Type-punning with Union

Check this snippet:

union a_union {
  int i;
  double d;
};

int f() {
  union a_union t;
  t.d = 3.0;
  return t.i;
}

Does the code work to whatever expection you may have?

This is actually one form of type punning through aliasing in union.

According to C++, e.g., C++2023 (N4928) 11.5.1, “At most one of the non-static data members of an object of union type can be active at any time”. The last written value is t.d, so only that member is active. Reading t.i is therefore undefined behavior (UB) in C++.

This practice has been widely used in the C world, and C permits this. Therefore, often times C++ programmers, especially who have legacy code from C, want this to work. Although C++ standard treats this as UB, some compilers support this:

Do check limitations in the URL above, for example, you cannot take the address and do pointer magic.

Example Usage

Let’s say you have existing code:

#include <iostream>

struct Vec3 {
    float f[3];

    float& operator()(size_t index) {
        return f[index];
    }
    float& operator[](size_t index) {
        return f[index];
    }
};

int main() {
    Vec3 v;
    v(0) = 1;
    v[1] = 2;
    v.f[2] = 5;
    std::cout << v[0] << ", " << v(1) << ", " << v.f[2] << "\n";
}

You are happy with the code. But then one day someone comes and asks, “Your Vec3 class is great. I have a lot of existing code that uses a similar 3D vector class, but they access the data through .x, .y and .z. Can I use your nice Vec3 class instead without modifying a lot of my code?”

Basically, you can redefine your Vec3 class, but you want the new Vec3 class to support the current public API, as well as new API that allows access members through .x, .y and .z.

Obviously you cannot add new data members like x, y, and z, because that would have two copies for each value, and there’s no way that you can keep them always in sync as clients can freely modify x or f[0] independently. Adding x, y, and z as references to f members seems to work, but it makes the class much larger, also not memcpy safe.

Enter union, more specifically, anonymous union.

#include <iostream>

struct Vec3 { 
    union{
        struct { 
            float x;
            float y;
            float z; 
        }; 
        float f[3];
    };

    float& operator()(size_t index) {
        return f[index];
    }
    float& operator[](size_t index) {
        return f[index];
    }
}; 


int main() {
    Vec3 v;
    v(0) = 1;
    v[1] = 2;
    v.f[2] = 5;

    std::cout << v[0] << ", " << v(1) << ", " << v.f[2] << "\n"; // Line AA
    std::cout << v.x << ", " << v.y << ", " << v.z << "\n";      // Line BB
}

Line AA demonstrates the API of old Vec3, and Line BB demonstrates the new API. Both are supported.

For this two happen, there are two steps:

  • Anonymous struct. In the union definition, the anonymous struct provides x/y/z members. The anonymous struct is both a type definition and an anonymous member object of the union. Being anonymous struct member allows user of parent object, i.e., the union, to directly access the data through .x, .y and .z.
  • Anonymous union. In the Vec3 class, it has the anonymous union type and the same time as anonymous union member of the class. This allows the Vec3 class user to access union members directly, because the union member of Vec3 does not have a name.

Combining the two steps above, now client of Vec3 can access through .x, .y and .z directly.

Theoretically this approach falls into the UB land, but due to the support of extension in GCC and Clang, this is actually safe to do.

Posted in C++ | Tagged: , , , , , , , , , | Leave a Comment »

Retirement Plans: 401(k), IRA, Traditional or Roth

Posted by binglongx on November 9, 2024

Comparison Table of 401(k), Roth 401(k), IRA, and Roth IRA

Features401(k) (Traditional)Roth 401(k)IRA (Traditional)Roth IRA
Opening PlanEmployer sponsoredEmployer sponsoredAny individualAny individual
Contributionbefore-tax
(tax deferred to distribution)
(contribution deducted from W2 taxable income)
after-tax
(contribution not deducted from W2 taxable income)
before-tax
(tax deferred to distribution)
(contribution deducted from W2 taxable income)
after-tax
(contribution not deducted from W2 taxable income)
Distributiontaxed as income
(on contributions and gains)
tax-free
(on contributions and gains)
taxed as income
(on contributions and gains)
tax-free
(on contributions and gains)
Contribution limit[Combined with Roth 401(k)]

2022: $20,500
2023: $22,500
2024: $23,000
2025: $23,500
[Combined with 401(k)]

2022: $20,500
2023: $22,500
2024: $23,000
2025: $23,500
[Combined with Roth IRA]

2022: $6,000
2023: $6,500
2024: $7,000
2025: $7,000
[Combined with IRA]

2022: $6,000
2023: $6,500
2024: $7,000
2025: $7,000
Employer matchEmployer may matchEmployer may match

Match goes to your 401(k) pre-tax. It may allow moving to Roth 401(k) after paying tax.
N/AN/A
Contribution limit
Employee + Employer
[Combined with Roth 401(k)]

2023: $66,000 or income
2024: $69,000 or income
2025: $70,000 or income
[Combined with 401(k)]

2023: $66,000 or income
2024: $69,000 or income
2025: $70,000 or income
N/AN/A
Catch-up contribution
(age 50+)
[Combined with Roth 401(k)]

2022: $6,500
2023: $7,500
2024: $7,500
2025: $7,500
2026:
– <$145k incomer: $7,500
– >$145k incomer: $0.
must contribute to Roth 401(k)
[Combined with 401(k)]

2022: $6,500
2023: $7,500
2024: $7,500
2025: $7,500
2026: $7,500
[Combined with Roth IRA]

2025: $1,000
[Combined with IRA]

2025: $1,000
(Modified AGI) Income limit
to be able to contribute
(married file jointly)
No income limitNo income limitPhase-out range:

You in work retirement plan:
2024: $123k-$143k

Spouse in work retirement plan:
2024: $230k-$240k

Neither in work retirement plan:
No income limit
Phase-out range:

2023: $218k-$228k
2024: $230k-$240k
2025: $236k-$246k
(Modified AGI) Income limit
to be able to contribute
(single / household head)
No income limitNo income limitPhase-out range:

2023: $73k-$83k
2024: $77k-$87k
Phase-out range:

2023: $138k-$153k
2024: $146k-$161k
2025: $150k-$165k
InvestmentPlan may restrictPlan may restrictMore flexibleMore flexible
Fees and costsMay be higherMay be higherMay be lowerMay be lower
Employment termination– leave as;
– transfer to new job’s plan;
– rollover to IRA;
– take distribution (may have penalty)
– leave as;
– transfer to new job’s plan;
– rollover to Roth IRA;
– take distribution (may have penalty)
No impactNo impact
Withdrawl age59.55+ year account
and
(59.5
or disabled or death)

Special rule if age>55 and job termination.
59.55+ year account
and
(59.5
or
exceptions: disabled, 1st home buy)
Early withdrawl penalty10% penalty 10% penalty and tax complexity on gain10% penalty 10% penalty
and tax on gain
Required Minimum Distributions (RMD)Must begin at 73.
Amount depends on balance and life expectancy.
no requirement if alive;
leave to your heirs;
Must begin at 73.
Amount depends on balance and life expectancy.
no requirement if alive;
leave to your heirs;

Backdoor Roth IRA

If you are a high income earner, you may not be able to contribute to a Roth IRA account (unrelated, but neither traditional IRA). For this, you may consider to create a backdoor Roth IRA. This is legal, but it has pros and cons, and may not be suitable for everyone.

You can rollover some traditional IRA or 401(k) balance to a Roth IRA, or close and convert a traditional IRA or 401(k) account to a Roth IRA. This rollover or conversion is moving pre-tax money to after-tax, and you owe income tax for the year immediately.

Note that rollover or conversion is not subject to contribution limit to Roth IRA, which is often low ($7,000 in 2025). Basically, you can rollover or conversion any amout from traditional accounts to Roth IRA. But of course, you owe more income taxes if you convert more, potentially pushing to a higher tax bracket.

For people that don’t have traditional IRA, backdoor Roth IRA means moving/converting money from (traditional) 401(k) to Roth IRA by adding moved money as taxable income for the year, disregarding income limit that would otherwise apply. Depending on how much is moved/converted, this may increase your W2 taxable income considerably.

If you can contribute to Roth 401(k), Roth IRA is attractive. They serve the same goal (tax-free on gains), but Roth 401(k) has less restrictions.

In a sense, employer sponsored Roth 401(k) is like a backdoor, since now you get access to a “Roth” account, although Roth 401(k) is not Roth IRA, it is better than Roth IRA.

Mega Backdoor Roth

A lot of employer sponsored retirement plan allows you to contribute to both 401(k) and Roth 401(k) accounts. However, take this example:

  • You contribute $13,500 to 401(k) and $10,000 to Roth 401(k), reaching employee contribution limit $23,500.
  • Your employer matches 6% of your income, say $10,000. This could go into your 401(k), or proportionally into both 401(k) and Roth 401(k).
  • The total limit is $70,000.
  • There is a $70,000 – $23,500 – $10,000 = $36,500 hole. What can you do with it?

The answer is that, you may use it as “after-tax 401(k) contributions”. This is neither pre-tax nor after-tax contribution, but somewhere in between.

Type of 401(k) contributionContributionWithdrawl in retirementNotes
Pre-taxContribute pre-taxPay ordinary income tax on contributions and gains;
RothContribute after-taxPay no tax on contributions and gains;
After-taxContribute after-taxPay no tax on contributions;
Pay ordinary income tax on gains;
Maybe not eligible for employer match

It apperas that after-tax 401(k) contribution does not have benefits: after all, you can take cash and invest yourself, and the gain will again be taxed (but as short term/long term gains).

The real benefit is that, the after-tax 401(k) contribution can be converted to a Roth account. The money can be moved to a Roth 401(k) account, so called in-plan Roth conversion. When the conversion happens, there is no tax for the amount of your contributions, but any gains will incur tax. Normally, you need to (set up) periodically rollover “after-tax 401(k) contributions” to Roth 401(k).

The reason that this is called mega backdoor, is because of the large amount it can convert into Roth. In the example above, this amount is larger than half of the $70,000 limit of 401(k) contribution.

In Fidelity, go to Contributions -> Change Contributions -> Contribution Maximizer, under “In-Plan Roth Conversion”, choose “Convert After-Tax to Roth” is basically Mega Backdoor Roth. If this is the first time, refreshing the page may show choice reverted – wait for one day or two to show correctly.

References

Posted in Life | Tagged: , , , , , , , , , | Leave a Comment »