Search code examples
c++reinterpret-castc++20const-cast

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.

E.g.,:

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 );
}

Solution

  • Generally speaking, it's not safe to use const_cast to cast away constness 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.