Cycling: visit Michigan Central Station

I went on another Detroit bike ride today with Andy, Kevin and Nick. Same meeting place (Belle Isle). The intent was to check out the newly opened Ralph C. Wilson Jr. Centennial Park. Not just because it’s a 22-acre park on the riverfront, but because it extends the River Walk. The area that has been locked off from the public on the river side of the Riverfront Towers for many years is now finally open to pedestrians and cyclists.

Unfortunately, we didn’t realize the park was closed to cyclists this weekend due to grand opening festivities. Bad news for us, but the good news is that it looked very busy. The smell from the food trucks was intoxicating, and it looked to me that there were thousands of people having a great time! Unfortunately, none of us brought bike locks with us, so we couldn’t partake.

However, we found our way to the Southwest Greenway to get to Michigan Central Station, which I’ve had on my list since the public reopening last summer. I had not seen it since it was a landmark of Detroit decay (all the windows broken out, roof falling apart, etc.).

Today, October 25, 2025… more than a year beyond the completion of the restoration and opening to the public… it’s beautiful. Many thanks to everyone involved. The contractors that performed all the work, the engineers, the city, the residents and taxpayers, and Ford.

A picture of the outside from today:

And a couple from inside:

Despite the fact that there’s not much “public” about this building and the campus it sits on, it is no longer an eyesore or prominent display of industrial decay. It connects to the Ralph C. Wilson Jr. Centennial Park (and hence the River Walk) via the Southwest Greenway. A nice campus in a fantastic location, no longer dotted with a reminder of industrial decay and abandonment but instead a nice building of restored grandeur. Bravo!

I’ve been to Detroit 3 times this year for cycling. It’s an hour drive to get there and another hour to return. It’s the same for my friends. It’s worth it because it’s a fun place to ride. I honestly feel safer riding in Detroit than on the rural roads in my area. The greenways are attracting cyclists from outside the city.

I don’t remember where I saw it, but I read a story last year that predicted that Detroit could become a cycling Mecca in the U.S. in the next decade. If I squint (mostly to block out January and February weather), I can see that happening. Belle Isle plus the River Walk plus the other greenways… very nice leisure cycling.

30 more miles of rail-to-trail cycling today

Mom and I rode 30 more miles of rail-to-trail on Saturday September 27. All of the Michigan Air Line Trail and the Huron Valley Trail, in both directions. Both fully paved, both very straight, and both very flat but for the bridge over M5 on the Michigan Air Line Trail.

Mom shattered her record for longest bike ride; her previous record was 22 miles. I haven’t been recording all of my miles this season, but I think today would put me close to a record for a single ride this year (but not my record for daily total).

Mom has steadily gotten much better on the bike. I’m so proud of her! Today she got in some practice pedaling out of the saddle, as well as just standing on the bike.

To date, the list of trails/rides I’ve done this season:

  • Paint Creek Trail (twice with mom)
  • Holly Recreation Area (twice with mom)
  • Belle Isle + Detroit River Walk + Dequindre Cut (once with friends, once with mom)
  • Flint River Trail (with mom)
  • Polly Ann Trail (with mom)
  • Michigan Air Line Trail (with mom)
  • Huron Valley Trail (with mom)
  • Ann Arbor ride (with friends)
  • Algonac ride with my friend Andy

Exploring reflection in C++26 (P2996)

I’ve been exploring C++ reflection a bit, using the Bloomberg fork of clang. I’ve yet to get my head fully around the syntax and the implications, but I have an obvious use case: more serialization functionality in libDwm.

In order to play around in ‘production’ code, I needed some feature test macros that are not in the Bloomberg fork in order to conditionally include code only when C++26 features I need are present. P2996 tentatively proposed __cpp_impl_reflection and __cpp_lib_reflection and hence I added those. I also need features from P3491, whose proposed test macro is __cpp_lib_define_static which I also added. Finally, I added __cpp_expansion_statements (feature test macro for P1306).

Technically, __cpp_lib_reflection and __cpp_lib_define_static should be in <meta>, but I added them to the compiler built-ins just because it’s convenient for now.

I’ve run into some minor gotchas when implementing some generic reflection for types not already covered by serialization facilities in libDwm.

As an example… what to do about deserializing instances of structs and classes with const data members. Obviously the const members can’t be cleanly written to without going through a constructor. I haven’t given much thought to it yet, but at first glance it’s a bit of a sore spot.

Another is what to do about members with types such as std::mutex, whose presence inside a class would generally imply mutual exclusion logic within the class. That logic can’t be ascertained by reflection. Do I need to lock the discovered mutex during serialization? During deserialization too? Do I serialize the mutex as a boolean so that deserialization can lock or unlock it, or skip it?

For now, since we have P3394 in the Bloomberg clang fork, I’ve decided that using annotations that allow members of a structure or class to be skipped is a good idea. So in my libDwm experiment, I now have a Dwm::skip_io annotation that will cause Read() and Write() functions to skip over data that is marked with this annotation. For example:

  #include <sstream>
  #include "DwmStreamIO.hh"

  struct Foo {
    [[=Dwm::skip_io]] int  i;
    std::string            s;
  };

  int main(int arc, char *argv[]) {
    Foo  foo1 { 42, "hello" };
    Foo  foo2 { 99, "goodbye" };

    std::stringstream  ss;
    if (Dwm::StreamIO::Write(ss, foo1)) {
      if (Dwm::StreamIO::Read(ss, foo2)) {
        std::cout << foo2.i << ' ' << foo2.s << '\n';
      }
    }
    return 0;
  }

Would produce:

  99 hello

The i member of Foo is neither written nor read, while the s member is written and read. Hence we see that foo2.i remains unchanged after Read(), while foo2.s is changed.

Chasing pointers

A significant issue with trying to use reflection for generic serialization/deserialization: what to do with pointers (raw, std::unique_ptr, std::shared_ptr, et. al.). One of the big issues here is rooted in the fact that for my own use, reflection for serialization/deserialization is desired for structures that come from the operating system environment (POSIX, etc.). Those structures were created for C APIs, not C++ APIs, and pointers within them are always raw. Generically, there’s no way to know what they point to. A single element on the heap? An array on the heap? A static array? In other words, I don’t know if the pointer points to one object or an unbounded number of objects, and hence I don’t know how many objects to write, allocate or read.

Even with allocations performed via operator new[], we don’t have a good means of determining how many entries are in the array.

For now, I deny serialization / deserialization of pointers, with one experimental exception: std::unique_ptr whose deleter is std::default_delete<T> (implying a single object).

Reflection-based code is easier to read

Let’s say we have a need to check that all the types in a std::tuple satisfy a boolean predicate named IsStreamable. Before C++26 reflection, I’d wind up writing something like this:

    template <typename TupleType>
    consteval bool TupleIsStreamable()
    {
      auto  l = []<typename ...ElementType>(ElementType && ...args)
        { return (IsStreamable<ElementType>() && ...); };
      return std::apply(l, std::forward<TupleType>(TupleType()));
    }

While this is far from terrible, it’s not much fun to read (especially for a novice), and is still using relatively modern C++ features (lambda expressions with an explicit template parameter list, and consteval). Not to mention that std::apply is only applicable to tuple-like types (std::tuple, std::pair and std::array). And critically, the above requires default constructibility (note the TupleType() construct call). Because of this final requirement, I often have to resort to the old school technique (add a size_t template parameter and use recursion to check all element types) or the almost as old school technique of using make_index_sequence. Also note that real code would have a requires clause on this function template to verify that TupleType is a tuple-like type, I only left it out here for the sake of brevity.

The equivalent with C++26 reflection:

    template <typename TupleType>    
    consteval bool TupleIsStreamable()
    {
      constexpr const auto tmpl_args =
        define_static_array(template_arguments_of(^^TupleType));
      template for (constexpr auto tmpl_arg : tmpl_args) {
        if constexpr (! IsStreamable<typename[:tmpl_arg:]>()) {
          return false;
        }
      }
      return true;
    }

While this is more lines of code, it’s significantly easier to reason about once you understands the basics of P2996. And without changes (other than renaming the function), this works with some other standard class templates such as std::variant. And with some minor additions, it will work for other standard templates. The example below will also handle std::vector, std::set, std::multiset, std::map, std::multimap, std::unordered_set, std::unordered_multiset, std::unordered_map and std::unordered_multimap. We only check type template parameters, and if ParamCount is non-zero, we only look at the first ParamCount template parameters:

    template <typename TemplateType, size_t ParamCount = 0>    
    consteval bool IsStreamableStdTemplate()
    {
      constexpr const auto tmpl_args =
        define_static_array(template_arguments_of(^^TemplateType));
      size_t  numParams = 0, numTypes = 0, numStreamable = 0;
      template for (constexpr auto tmpl_arg : tmpl_args) {
        ++numParams;
        if (ParamCount && (numParams > ParamCount)) {
          break;
        }
        if (std::meta::is_type(tmpl_arg)) {
          ++numTypes;
          if constexpr (! IsStreamable<typename[:tmpl_arg:]>()) {
            break;
          }
          ++numStreamable;
        }
      }
      return (numTypes == numStreamable);
    }

We can use this with std::vector, std::deque, std::list, std::set, std::multiset, std::unordered_set and std::unordered_multiset by using a ParamCount value of 1. We can use this with std::map, std::multimap, std::unordered_map and std::unordered_multimap by using a ParamCount value of 2. Hence the following would be valid calls to this function (at compile time), just as a list of examples:

    IsStreamableStdTemplate<std::array<int,42>>()
    IsStreamableStdTemplate<std::pair<std::string,int>>()
    IsStreamableStdTemplate<std::set<std::string>>()
    IsStreamableStdTemplate<std::vector<int>,1>()
    IsStreamableStdTemplate<std::deque<int>,1>()
    IsStreamableStdTemplate<std::list<int>,1>()
    IsStreamableStdTemplate<std::map<std::string,int>,2>()
    IsStreamableStdTemplate<std::tuple<int,std::string,bool,char>>()
    IsStreamableStdTemplate<std::variant<int,bool,char,std::string>>()

Early thoughts

I have some initial thoughts about what we’re getting with reflection in C++26.

I think that the decision to approve P2996 for C++26 was a good decision. Having std::meta::info be an opaque type to the user with a bunch of functions (more can be added later) to access it is a good thing. From just the simple examples here, it’s pretty easy to see how it’s going to make some code much easier to understand, even without doing anything fancy (noting that I only used a single splice in each example).

I’ve done my fair share of template metaprogramming over the years. While it’s satisfying to be able to do it right when it’s the best (or only) option, it’s generally much more work than I’d like. And more than once I’ve found the cognitive load to be much higher than I’d like. I probably can’t count the number of times that I’ve gone to modify some template metacode 3 years after writing it, only to find that I underestimated the time to make a change just due to the difficulty of comprehending the code (regardless of the quality of the comments and names). This is especially true when I haven’t recently been deep in this kind of code. It has only gotten worse on this front over time; standard C++ has never been a small, simple language. But despite many features in C++11 and beyond being very useful, the scope of the language is now so big that there are probably no programmers on the planet that know the whole language and standard library. It’s too much for any one person to hold in their head.

One other thought is a caution for what some people are expecting without yet digging into details. For example, what we’re getting is not a panacea for serialization and deserialization. Yes, it will indeed be useful for such activities. But it doesn’t magically solve concurrency issues, nor the legacy issues with pointers, unbounded arrays, arrays decaying to pointers, etc. Even our smart pointers introduce unsolved issues for serialization and deserialization. But it’s the legacy stuff we often need from our operating system that will remain largely unsolved for the foreseeable future, as well as any other place where we are forced to interact with C APIs that were not designed for security, safety, and concurrency.

I’m optimistic that what we’re getting is going to be incredibly useful. While I’ve only scratched the surface in this post, I’m already at the point in my own code changes where I wish we had all the voted-in papers in our compilers today. The increased number of features I’m going to be able to cleanly add to my libraries is significant. The refactoring I’m going to be able to do is also significant (already underway with conditional compilation via feature macros I can later remove). I’m anxiously awaiting official compiler support!

More cycling trails 2025

Mostly just a note to myself…

Mom and I have now cycled the Flint River Trail and 22 miles of the Polly Ann Trail. Both are very nice, but I have 2 caveats:

  • The Flint River Trail is confusing (at best) south of Dort Highway (several on-street detours without sufficient signage).
  • If it hasn’t rained in a while, the Polly Ann Trail can be hard on a bicycle drivetrain. This is due to the trail being largely composed of fine crushed limestone, which winds up coating the whole bike with fine stone powder.

Neither of these caveats would prevent me from riding them again.

Annual Detroit River Walk, Dequindre Cut and Belle Isle bike ride, 2025

I think my friend Andy and I have turned this into an annual event. This year was only the second year we’ve done this ride, but it’s really nice and worth doing at least once a year.

Lifelong friends Nick and Kevin joined us this year. We started on Belle Isle, which was convenient parking-wise and let us bring coolers with food and drink for later. We rode off of Belle Isle to the River Walk to Dequindre Cut, rode the length of the Dequindre Cut, returned to the River Walk and rode it to the end, then rode the fenced-in perimeter of the new Ralph C. Wilson Jr. Centennial Park just to get a peek at its progress. It looks like most of what remains to be done is landscaping, so maybe we’ll see a September opening? All I’ve seen PR and press-wise is “opens this fall”. Hopefully that doesn’t mean the second week of December. It looks like it’ll be a fun place for kids, and it’s 22 acres!

We then returned to our spot on Belle Isle to rehydrate and have a picnic lunch. We then rode the perimeter of Belle Isle. I didn’t keep track of total miles today and didn’t start the Workout app on my watch, since I knew it was a leisure ride. But Andy says we did 22+ miles. So nice to have this much biking space on the Detroit River front and Belle Isle!

I really enjoyed it, and I think everyone else did too.

Nick, me, Andy and Kevin (left to right) on Belle Isle, August 16, 2025
Detroit River Walk August 16, 2025 (pic courtesy of Kevin)
Dequindre Cut August 16, 2025 (pic courtesy of Kevin)
Detroit River Walk August 16, 2025 (pic courtesy of Kevin)
View of Detroit from Belle Isle paved bike path (pic courtesy of Kevin)
View of Detroit from Belle Isle bike path

My bicycling in 2025 and the gamification of fitness

I haven’t been logging my rides on the web site this season. The primary reason is that I met my weight loss goal last season and have kept the weight off. I’m about 25 pounds lighter than I was at the start of the 2024 season (from 178 pounds to 153 pounds).

Another reason is that I started the season late in terms of riding as many days as possible. I didn’t ride much in May.

I also just don’t need the extra motivation, and some of my rides are mostly leisure rides. Some with my mom, some with my friend Andy, many alone. Cycling has always been fun to me, and after losing 25 pounds, I don’t really have goals beyond the big psychological benefits of cycling. Getting outside, getting the heart pumping, getting endorphins (which has helped my sleep tremendously). I’m also paying attention to different metrics than I did last year, mainly time in heart rate zones 2 and 3. I don’t really set goals against these metrics, but they’re useful for identifying leisure rides versus workout rides and making sure I’m completing a reasonable number of workout rides each week.

And finally, but maybe most importantly, the gamification stuff in the Apple Fitness app provides enough motivation to push me a bit at times. As an example, today I logged 31.25 miles, all in ECO 1 (lowest pedal assist) on my 90 pound e-bike, burning 1,423 calories in the process. But it was split across 2 rides, the first being 24.65 miles. After seeing the data, I realized I could beat my daily move calories record and my daily exercise record with about 5 more miles, so off I went for another 6.6 miles (a ride to the local dairy farm for skim milk, with an extra hill climb tacked on). All in the name of getting 2 new awards in Apple Fitness: a New Move Record (for the calories) and a New Exercise Record (151 minutes).

Two years ago, I would have said that the gamification in Apple Fitness would not work on me. I was wrong. I knew this last season; even without using much data from Apple Fitness, I had done my own gamification by setting mileage and calorie goals and then going after them. But this year I’ve gone a little easier on myself in terms of mileage goals, and just used the Fitness awards as occasional extra motivation.

As of July 26, my e-bike odometer is at 2,039.1 miles. That accounts for last season and this season so far. I will not match last season’s mileage during this season (due largely to getting started much later). I feel a little bit disappointed, but really my goals have changed. I’m not looking to lose more weight, and my goal is more about maintenance, heart strength, endorphins and enjoying my rides.

Almost done with bedroom 1 remodel

This room was a lot of work. The first two pictures are how it looked the morning of May 27, 2025 after I reinstalled the freshly painted window sashes and the last piece of new white window jamb liner.

The next two pictures are from the evening after I moved an old rug into the room. This rug is too big for the room and not the colors I would choose for this room, but the rug was taking up space in the master bedroom (rolled up) for a long time and it was time to move it out of the way. It can live in this room for a while. The upside is that it’s a nice thick 100% wool rug that I’ve had for more than 25 years. It’s nice under foot, and was professionally cleaned before it was rolled up.

Without going into every little detail, but enough to explain why this room took so long to complete…

New ceiling box to allow a ceiling fan (installed from the attic). New 2-gang box to replace 1-gang box to allow separate smart controls (HomeKit compatible) for the ceiling fan and light. “Hey Siri, turn the ceiling fan to 50%”. New 14/3 wire from the switch box to the ceiling box to allow the new controls.

All new trim, including custom entablatures for the door, window and closet doors.

3-piece crown moulding. I had to move the cold air return opening down about 1.5″ to accommodate it. New Victorian style cold air return grill installed.

Primer and paint on ceiling, walls and trim.

Hickory hardwood floor to replace the dingy blue carpet. I primed the subfloor since there were some stains of unknown origin. Then Floor Muffler underlayment, then the hardwood. And yes, I did the closets, including new baseboard and moulding and new primer and paint after I removed the closet organizer structures (not easy).

New floor registers that I bought from Signature Hardware for this room many years ago but hadn’t installed them until now.

New window jamb liners; from gray (‘stone’) to white, so I could paint the window sashes and grills white.

Entrance door issues resolved. I swear the door was originally hung by an untrained monkey; it was so crooked that it cleared the stop moulding at the bottom on the latch side by almost 1/4″. I never closed the door, so I hadn’t noticed until I did the hallway trim work. I reshimmed the jambs at that time, but there was so little room to work (adjacent perpendicular walls on both sides of the door), I wound up with a snug door. Which was fine until I primed and painted and wound up with rubbing. I planed the hinge side of the door and recut the hinge mortises to correct for it, and moved the door stop moulding about 1/16″. It’s all good now, the door closes snugly but there’s no rubbing or squeaking.

The fastest, least expensive, reliable sacrificial table saw fence

I’ve seen a variety of sacrificial table saw fences for contractor table saws like my Bosch 4100. My main issue with all of them is that they either take too long to make or they cost too much. Or take up too much space on a saw that’s already limited in rip capacity.

An example of one that costs too much is any that involves Microjig Matchfit dovetail clamps. Today (March 18, 2025), they’re $45 for a pair. I love some of Microjig’s products, but $45 for a pair of clamps that’s just holding a sacrificial fence to my table saw fence is absurd. In general, my sacrificial fences stay in place until I need to replace them from wear or I need the special ones that are intentionally cut to accommodate blade-width edge cuts or using the dado set to cut rabbets. In other words, the clamping mechanism is semi-permanent; I am not loosening or tightening the clamps but once in a while. It’s also specific and fixed; it doesn’t need versatility. Finally, cutting dovetails takes way longer than other means. The cutting itself is quick, but I have to load the dovetail bit in the router, set its depth, clamp the work, etc.

An example of one that takes too long and uses too much space: parallel boards with space between for typical F or C clamps.

All that most of us want is a piece of scrap MDF held by fence clamps that only require one hole to be drilled per clamp. Fence clamps are less expensive than dovetail clamps; the Milescraft 7209 in a set of 4 is $23.99 today from Amazon. But there’s an even cheaper option if your fence isn’t too wide: Bessey TK-6 table clamps, which are $7.69 for a pair on Amazon as of right now. That’s what I’m using on my Bosch 4100. Drill a pair of 5/16″ holes in a piece of MDF and you’re ready to go.

Building a bench for my kitchen

I’ve been working on a bench for my kitchen. I have no mud room, and in fact the entrance from the garage dumps directly into the kitchen. There was originally no place to remove coat/boots/shoes/hat/gloves, and nowhere to store them. To get to the coat closet at the front door, you have to walk through the kitchen and family room. This is highly inconvenient.

Last month I finished two coat racks, one for the front entrance and one for the kitchen. Nothing special about them except the one by the front door is tall enough to hang my bicycle helmets without them banging on the wall. They’re solid oak, and hung on the wall with concealed French cleats. They both have a shelf above the hooks.

I created a small console table for the front foyer, mostly as a place to dump keys/wallet and to hold a wooden box on the bottom shelf for my cycling caps and cycling gloves which I use daily in season.

For the kitchen, I wanted a bench I could sit on when putting boots or shoes on or removing them. I also wanted it to be roughly dining chair seat height, so it can double as kitchen table seating when I have the extension inserts in the table (which will seat 10 but I only have 4 matching chairs and don’t really want more). And I also wanted a spot to stash my winter boots in the winter and my in-house shoes (Vans slip-ons). I have a 15″x20″ polypropylene boot tray I’d like it to accommodate.

I designed the bench in SketchUp, and I’m done with the main assembly part. The legs, stretchers, rails, top frame and cove moulding are all solid red oak, dyed with TransTint mahogany brown. The stiles are solid maple, with no dye or stain. The main horizontal surfaces are stone-look porcelain with a PEI IV rating, which are supported underneath by 3/4″ plywood (plus another 3/8″ of plywood in the case of the top piece). I like using porcelain for primary contact surfaces because it’s very durable, waterproof and man-made. It’s inexpensive versus real marble or granite, and rectified porcelain tiles tend to be very accurately sized perfect rectangles. And these days you can get them in almost any size you’d like. I have a wet tile saw (actually two, a typical sliding-tray saw and a handheld with a hose feed that I use for really big tiles, say 30″x60″ and bigger like I used for my desks). But in this case, I found 15″x30″ tiles on clearance at Menards that I like for this bench. And that was the right size to hold a typical boot tray on the bottom shelf.

The picture below is from final fitment. I hadn’t attached the top to the base yet, it’s just sitting on top of the base. The tiles aren’t adhered and grouted. And I haven’t completed the finish work (no polyurethane yet, just dewaxed shellac to prevent dye and glue migration). But everything fits perfectly, it sits flat on the floor (no rocking), and it’s rock solid which was important to me since I’ll bet sitting on it daily, dropping my backpack and groceries on it, etc.

The design doesn’t include a seat back, and that’s intentional. The bench can be repurposed as a coffee table.

Painting the foyer and first floor hallway

Lots of work here, mostly due to the need for a lot of wall patching. I’ve been living with very dinged up walls since I bought the house, all from the previous owners. As an example, here’s a short wall while I was patching and spot priming with Zinsser B-I-N.

After patching and priming, it looked like this:

I hadn’t intended to paint the ceiling, but I decided to patch a tiny drywall joint settling crack on the ceiling and two small holes near the master bedroom (not sure what was previously mounted there). I didn’t have a lot of ceiling area to paint, but it needed to be done first so I wouldn’t wind up with spatter on the walls. As of Thanksgiving, I had put 2 thin coats of Kilz Original primer on the walls and ceiling and the first coat of flat ceiling paint on the ceiling.

The color I’m using on the walls is a very light yellow/cream, Sherwin-Willams ‘Vanillin’ (SW 6371) in matte sheen.

As of December 3rd, the painting is done. I’ll clean up and remove the remaining masking tape and floor protection tomorrow.