Did my compiler ignore my unused static thread_local class member?

I found this information in "ELF Handling For Thread-Local Storage" which can prove @L.F. 's answer

In addition the run-time support should avoid creating the thread-local storage if it is not necessary. For instance, a loaded module might only be used by one thread of the many which make up the process. It would be a waste of memory and time to allocate the storage for all threads. A lazy method is wanted. This is not much extra burden since the requirement to handle dynamically loaded objects already requires recognizing storage which is not yet allocated. This is the only alternative to stopping all threads and allocating storage for all threads before letting them run again.

There is no problem with your observation. [basic.stc.static]/2 prohibits eliminating variables with static storage duration:

If a variable with static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in [class.copy].

This restriction is not present for other storage durations. In fact, [basic.stc.thread]/2 says:

A variable with thread storage duration shall be initialized before its first odr-use and, if constructed, shall be destroyed on thread exit.

This suggests that a variable with thread storage duration need not be constructed unless odr-used.

But why is this discrepancy?

For static storage duration, there is only one instance of a variable per program. The side effects of construction thereof can be significant (kinda like a program-wide constructor), so the side effects are required.

For thread local storage duration, however, there is a problem: an algorithm may start a lot of threads. For most of these threads, the variable is completely irrelevant. It would be hilarious if an external physics simulation library that calls std::reduce(std::execution::par_unseq, first, last) ends up creating a lot of foo instances, right?

Of course, there can be a legitimate use for side effects of the construction of variables of thread local storage duration that are not odr-used (e.g., a thread tracker). However, the advantage for guaranteeing this is not enough to compensate for the aforementioned drawback, so these variables are allowed to be eliminated as long as they aren't odr-used. (Your compiler can choose not to do, though. And you can also make your own wrapper around std::thread that takes care of this.)