I write a lot of C++ code for Windows. There are a lot of Windows APIs which expose cool functionality and are implemented using COM; for me this currently means Direct2D, DirectWrite, Direct3D, DXGI, and Windows Imaging Component. In terms of presenting a compatible ABI, COM is a good thing (and is certainly nicer than straight flattening a C++ API to a C API). That said, COM looks extremely clunky in modern C++, for the following reasons:

Some of those reasons are fairly minor, others are a major nuisance, but in aggregate, their overall effect makes COM programming rather unpleasant. Some people take small measures to attack one of the reasons individually, such as CHECK_HRESULT macro for throwing an exception upon an unsuccessful HRESULT (but you still need to wrap each call in this macro), or a com_ptr<T> templated smart pointer which at least does AddRef and Release automatically (but you then need to unwrap the smart pointer when passing it as a parameter to a COM method). I think that a much better approach is to attack all of the problems simultaneously. It isn't as easy as writing a single macro or a single smart pointer template, but the outcome is nice-to-use COM rather than COM-with-one-less-problem.

As with switching on Lua strings, my answer is code generation. I have a tool which:

  1. Takes as input a set of COM headers (for example d3d10.h, d3d10_1.h, d2d1.h, dwrite.h, dxgi.h, and wincodec.h).
  2. Identifies every interface which is defined across this set of headers.
  3. For each interface, it writes out a new class (in an appropriate namespace) which is like a smart pointer on steroids:
    1. If the interface inherits from a base interface, then the smart class inherits from the smart class corresponding to the base interface.
    2. Just like a smart pointer, AddRef and Release are handled automatically by copy construction, move construction, copy assignment, and move assignment.
    3. For each method of the interface, a new wrapper method (or set of overloaded methods) is written for the class:
      1. If the return type was HRESULT, then the wrapper checks for failure and throws an exception accordingly.
      2. Out-parameters become return values (using a std::tuple if there was more than one out-parameter, or an out-parameter on a method which already had a non-void and non-HRESULT return type).
      3. Coupled parameter pairs (such as pointer and length, or pointer and IID) become a single templated parameter.
      4. Pointers to POD structures get replaced with references to POD structures, with optional pointers becoming an overload which omits the parameter entirely.
      5. Pointers to COM objects get replaced with (references to) their corresponding smart class (in the case of in-parameters and also out-parameters).

As an example, consider the following code which uses raw Direct2D to create a linear gradient brush:

// rt has type ID2D1RenderTarget*
// brush has type ID2D1LinearGradientBrush*
D2D1_GRADIENT_STOP stops[] = {
  {0.f, colour_top},
  {1.f, colour_bottom}};
ID2D1GradientStopCollection* stops_collection = nullptr;
HRESULT hr = rt->CreateGradientStopCollection(
  stops,
  sizeof(stops) / sizeof(D2D1_GRADIENT_STOP),
  D2D1_GAMMA_2_2,
  D2D1_EXTEND_MODE_CLAMP,
  &stops_collection);
if(FAILED(hr))
  throw Exception(hr, "ID2D1RenderTarget::CreateGradientStopCollection");
hr = rt->CreateLinearGradientBrush(
  LinearGradientBrushProperties(Point2F(), Point2F())
  BrushProperties(),
  stops_collection,
  &brush);
stops_collection->Release();
stops_collection = nullptr;
if(FAILED(hr))
  throw Exception(hr, "ID2D1RenderTarget::CreateLinearGradientBrush");

With the nice-COM headers, the exact same behaviour is expressed in a much more concise manner:

// rt has type C6::D2::RenderTarget
// brush has type C6::D2::LinearGradientBrush
D2D1_GRADIENT_STOP stops[] = {
  {0.f, colour_top},
  {1.f, colour_bottom}};
brush = rt.createLinearGradientBrush(
  LinearGradientBrushProperties(Point2F(), Point2F()),
  BrushProperties(),
  rt.createGradientStopCollection(
    stops,
    D2D1_GAMMA_2_2,
    D2D1_EXTEND_MODE_CLAMP));