简体   繁体   中英

Why doesn't const_cast work on arguments to std::function?

I'm providing a const and non-const variation of a member function where I reuse the const version to implement the non-const version as described in this answer per Scott Meyers books.

The const version takes an argument of type:

const std::function< void (const Foo &) > &

vs the non-const takes an argument of type:

const std::function< void (      Foo &) > &

In the implementation, I have to use reinterpret_cast because const_cast is not working.

Eg,:

const std::function< void (Foo &) > & Processor;
reinterpret_cast< const std::function< void (const Foo &) > & >( Processor );

vs

const std::function< void (Foo &) > & Processor;
const_cast< const std::function< void (const Foo &) > & >( Processor );

Wouldn't this be in the spirit of what const_cast is meant for? Is this just an oversight in the language definition to perhaps be fixed in C++2x or would const_cast never be in the spirit of things here?

Here is more complete code:

void Collection::ProcessCollection(const std::function< void (const Foo &) > & Processor) const
{
    for( int idx = -1 ; ++idx < m_LocalLimit ; )
    {
        if ( m_Data[ idx ] )
        {
            Processor( m_Data[idx] );
        }
    }

    const int overflowSize = OverflowSize();

    for( int idx = -1 ; ++idx < overflowSize ; )
    {
        Processor( (*m_Overflow)[ idx ] );
    }
}

void Collection::ProcessCollection(const std::function< void (Foo &) > & Processor)
{
    const Collection * constThis = const_cast< const Collection * >( this );
    const std::function< void (const Foo &) > & constProcessor
        = reinterpret_cast< const std::function< void (const Foo &) > & >( Processor );

    constThis->ProcessCollection( constProcessor );
}

Generally speaking, it's not safe to use const_cast to cast away const ness that appears in template arguments. For example, consider this (admittedly, somewhat contrived) code:

template <typename T> struct Wrapper {
    int x;
};

template <> struct Wrapper<char *> {
    double y;
};

Here, a pointer to a Wrapper<const char *> points to a very different object than a Wrapper<char *> , so doing a const_cast to turn a Wrapper<const char *> * into a Wrapper<char *> * would result in a pointer to a struct containing an int now pointing at a struct containing a double , breaking some language rule whose name eludes me at the moment. :-)

Since in general it's not safe to const_cast this way, the language spec doesn't allow for const_cast to be used like this, which is why in your case, even though the operation makes intuitive sense, the language disallows your code with const_cast .

I am fairly certain that using a reinterpret_cast here leads to undefined behavior, since the language considers std::function<T1> and std::function<T2> to be different, incompatible types when T1 and T2 aren't the same. It may happen to work on your system by pure coincidence, but I don't believe you can safely assume this will work.

reinterpret_cast here is undefined behavior. const_cast isn't appropriate, because the const ness of template parameters isn't something you can cast away.

The easy way to solve this is, well, don't. Get rid of some references. Swap who implements it.

void Collection::ProcessCollection(std::function< void (const Foo &) > Processor) const
{
  Collection * mutableThis = const_cast< Collection * >( this );

  mutableThis->ProcessCollection( Processor );
}

void Collection::ProcessCollection(std::function< void (Foo &) > Processor)
{
  for( int idx = -1 ; ++idx < m_LocalLimit ; )
  {
    if ( m_Data[ idx ] )
    {
      Processor( m_Data[idx] );
    }
  }

  const int overflowSize = OverflowSize();

  for( int idx = -1 ; ++idx < overflowSize ; )
  {
    Processor( (*m_Overflow)[ idx ] );
  }
}

std::function stores things that are call-compatible with it.

If you take a Foo& , you can call a function expecting a Foo const& with it. So you can store a std::function<void(Foo const&)> within a std::function<void(Foo&)> .

Wrapping something in a std::function can involve allocating. So you may want to find a high-quality function_view<Sig> to replace your use of std::function .

In another comment, you state that this code is in a critical loop. Eliminating std::function entirely is a good move, or at least reducing the type erasure taps and passing batches of data to it somehow.

The inversion of const/unconst is still appropriate; we implement const in terms of mutable instead of mutable in terms of const because one is a covariant operation, the other is a contravariant operation. See here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM