c++ – Avoid temporary copies in call to lambdas

Question:

I have the following class:

class Utf8Writer {
  struct ZoneData {
    uint32_t begin; // codepoint inicial.
    uint32_t end; // codepoint final.
  };

  const ZoneData *findCodepoint( uint32_t cp, FontId fid );
};

const ZoneData *Utf8Writer::findCodepoint( uint32_t cp, FontId fid ) {
  auto iter = std::find_if( fid->cbegin( ), fid->cend( ), [cp] ( ZoneData i ) { return ( cp >= i.begin ) && ( cp <= i.end ); } );

  return ( iter == fid->cend( ) ) ? nullptr : &( *iter );
}

It works perfectly, except that it is a bug . What I really wanted to do is:

const ZoneData *Utf8Writer::findCodepoint( uint32_t cp, FontId fid ) {
  auto iter = std::find_if( fid->cbegin( ), fid->cend( ), [cp] ( const ZoneData &i ) { return ( cp >= i.begin ) && ( cp <= i.end ); } );

  return ( iter == fid->cend( ) ) ? nullptr : &( *iter );
}

In fact, if we look at the doc for std::find_if( ) :

template< class InputIt, class UnaryPredicate > InputIt find_if( InputIt first, InputIt last, UnaryPredicate p );

where UnaryPredicate -> bool pred( const Type &a );

I understand that, in the first version, the compiler creates a temporary one, calling the constructor-copy by default . It turns out that the ZoneData class is more complex than shown: it includes std::vector and other little things , and its copy is not trivial (neither in time nor in memory usage).

  • Is it possible to avoid creating ZoneData in lambda calls, for future occasions, without touching the ZoneData class?

Answer:

It may be a good idea to use a static_assert that fails if the parameter of the lambda is not a reference, to achieve this we need to use the <type_traits> header and its std::is_reference utility:

This code will fail:

const ZoneData *Utf8Writer::findCodepoint( uint32_t cp, FontId fid ) {
  auto iter = std::find_if( fid->cbegin( ), fid->cend( ), [cp] ( ZoneData i )
//          'i' es una instancia, podría ser copia o movido ---> ~~~~~~~~~~
{
    // La aserción falla pues 'i' no es una referencia
    static_assert(std::is_reference_v<decltype(i)>, "Evita copiar el parametro!");
    return ( cp >= i.begin ) && ( cp <= i.end );
} );

While this should work without a hitch:

const ZoneData *Utf8Writer::findCodepoint( uint32_t cp, FontId fid ) {
  auto iter = std::find_if( fid->cbegin( ), fid->cend( ), [cp] ( const ZoneData &i )
//                     'i' es una referencia, no será copia ---> ~~~~~~~~~~~~~~~~~
{
    // La aserción NO falla pues 'i' no es una referencia
    static_assert(std::is_reference_v<decltype(i)>, "Evita copiar el parametro!");
    return ( cp >= i.begin ) && ( cp <= i.end );
} );
Scroll to Top