#1418 new fixme

Compile problems with `CommonType`

Reported by: Ichthyostega Owned by:
Priority: lesser Milestone:
Component: lumiera Keywords: QA sanity tooling evolution
Sub Tickets: Parent Tickets:

Description

As Lumiera Architect,
I want to keep an eye on strange compilation problems,
encountered in relation to our lib::CommonResult

Background

When several data sources need to be combined with a generic functor, the problem of type reconciliation arises. Over time, Lumiera used several approaches to overcome this hurdle. First we had some heuristics. Then we used the ternary operator — and as newer C++ standards also provided this technique packaged as std::common_type, we tried to adopt that, yet found its interface limiting.

Last week I thus developed a comprehensive solution from scratch, based on the same implementation technique, and taking lease from the implementation in the GNU LibStdc++.

But then I discovered that std::declval<TY>() adds a RValue-reference to the given type, since C++14. In itself this is not problematic, and might even seem to add some benefit. However, adding a RValue-reference to a type returned by-value from a function defeats the compiler's ability to perform the RVO optimisation. Newer compiler versions even warn when they detect such a situation.

Our use-case is seemingly different then the intended use of std::common_type and std::common_reference — we want a reconciled return type of a generated functor. So, given that we have our own implementation in place now, it seems obvious just to swap-out the use of std::declval<TY> and use a (not implemented) function definition TY yield() instead.

And here comes the catch

Once I swap out that little piece in the machinery, we get a confusing compile failure with GCC-14

src/lib/iter-explorer.hpp:423:24: error: static assertion failed: source iterator and result from the expansion must yield compatible values
423 | static_assert (can_reconcile,
| ^~~~~~~~~~~~~
src/lib/iter-explorer.hpp:423:24: note: 'lib::{anonymous}::_ExpanderTraits<lib::iter_explorer::BaseAdapter<lib::iter_explorer::StlRange<lib::Several<std::reference_wrapper<steam::engine::ProcNode> >&> >, lib::IterExplorer<lib::iter_explorer::BaseAdapter<lib::iter_explorer::StlRange<lib::Several<std::reference_wrapper<steam::engine::ProcNode> >&> > > >::can_reconcile' evaluates to false
src/lib/iter-explorer.hpp:428:15: error: no type named 'Type' in 'using lib::{anonymous}::_ExpanderTraits<lib::iter_explorer::BaseAdapter<lib::iter_explorer::StlRange<lib::Several<std::reference_wrapper<steam::engine::ProcNode> >&> >, lib::IterExplorer<lib::iter_explorer::BaseAdapter<lib::iter_explorer::StlRange<lib::Several<std::reference_wrapper<steam::engine::ProcNode> >&> > > >::_CommonT = struct lib::meta::CommonResult<std::reference_wrapper<steam::engine::ProcNode>&, std::reference_wrapper<steam::engine::ProcNode>&>' {aka 'struct lib::meta::CommonResult<std::reference_wrapper<steam::engine::ProcNode>&, std::reference_wrapper<steam::engine::ProcNode>&>'}
428 | using YieldRes = _CommonT::Type;
| ^~~~~~~~

This failure is triggered from a recursive processing pipeline in ProcNode

string
ProcID::genSrcSpec (Leads&  leads) const
{
return isnil(leads)? string{"-◎"}  // no leads => starting point itself is a source node
                   : "┉┉{"
                     + util::join(
                         explore(leads)
                           .expandAll([](ProcNode&  n){ return explore(watch(n).leads());  })
...                                                                             ▲▲▲▲▲ Compile-Error
...

some further observations

  • Looking into the type instantiations visible in the compile failure, it is obvious that the reconciliation test should succeed. Very clearly both legs in the relevant template are instantiated with essentially the same iterator pipeline.
  • Furthermore I have re-created a similar call structure in a test, and investigated the derived types with our usual type-inspection templates: there is no doubt that the SrcYield and the ResYield in _ExpanderTraits (at line 415 in iter-explorer.hpp) will alias to exactly the same result type, namely std::reference_wrapper<steam::engine::ProcNode>&
  • when invoked on its own, both the original implementation (with std::declval<TY>) and the changed implementation (with yield<TY>()) will compile just fine
  • and this is not surprising; there is nothing special with instantiating
    lib::meta::CommonResult<std::reference_wrapper<steam::engine::ProcNode>&, std::reference_wrapper<steam::engine::ProcNode>&>'
    
    ...that should just succeed and return the passed (identical) type as reconciled type.

assessment

This looks like a very nasty compiler bug. In my build it is totally reproducible. But that is a full build of Lumiera. Since regenerating the failure with a clean slate example was not possible, this seems to be a Heisenbug.

Furthermore, GCC-14 is in Debian/stable, and we cannot expect any compiler developer to care for that version, let alone attempt to set up a Lumiera build with all the dependencies.

And, last but not least, the solution with std::declval<TY>() does work (both in our implementation and in the LibStdC++). It produces slightly sub-optimal results sometimes though.

What to do??

  • wait for an opportunity to try this build with another compiler?
  • hope that the bug disappears magically by itself
  • maybe still find a way to make it reproducible??

Change history (0)

Note: See TracTickets for help on using tickets.