Почему const_cast не работает с аргументами std :: function?

Я предоставляю константные и неконстантные варианты функции-члена, где я повторно использую константную версию для реализации неконстантной версии, как описано в в этом ответе из книг Скотта Мейерса.

Версия const принимает аргумент типа:

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

vs неконстантный принимает аргумент типа:

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

В реализации я должен использовать reinterpret_cast, потому что const_cast не работает.

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

Разве это не было бы в духе того, для чего предназначен const_cast? Является ли это просто упущением в определении языка, которое, возможно, будет исправлено в C ++ 2x, или const_cast никогда не будет в духе вещей здесь?

Вот более полный код:

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

person WilliamKF    schedule 13.06.2019    source источник
comment
const_cast относится к типу, а не к подтипу как часть сигнатуры параметров.   -  person Eljay    schedule 14.06.2019
comment
В подходе, предложенном Скоттом, вы const_cast аргументы, чтобы вы могли вызвать функцию. Ваш код пытается привести тип функции (который никогда не будет благословлен стандартом).   -  person M.M    schedule 14.06.2019
comment
@KamilCuk Вопрос обновлен.   -  person WilliamKF    schedule 14.06.2019
comment
@ M.M Исправлено несовпадение < > и предоставлены полные функции.   -  person WilliamKF    schedule 14.06.2019
comment
подумайте о том, чтобы ProcessCollection был шаблоном функции, принимающим Callable. За счет наличия кода в заголовке он стал более лаконичным, гибким и оптимизируемым.   -  person M.M    schedule 14.06.2019
comment
@ M.M Это то, что я пытаюсь из-за комментариев в ответе.   -  person WilliamKF    schedule 14.06.2019


Ответы (2)


Вообще говоря, небезопасно использовать const_cast для отбрасывания constness, которое появляется в аргументах шаблона. Например, рассмотрим этот (правда, несколько надуманный) код:

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

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

Здесь указатель на Wrapper<const char *> указывает на совсем другой объект, чем на Wrapper<char *>, поэтому выполнение const_cast для превращения Wrapper<const char *> * в Wrapper<char *> * приведет к указателю на struct, содержащий int, теперь указывающий на struct, содержащий double, что нарушит некоторые языковое правило, название которого сейчас ускользает от меня. :-)

Поскольку в целом const_cast таким способом небезопасно, спецификация языка не позволяет использовать const_cast таким образом, поэтому в вашем случае, даже если операция имеет интуитивно понятный смысл, язык запрещает ваш код с const_cast.

Я вполне уверен, что использование reinterpret_cast здесь приводит к неопределенному поведению, поскольку язык считает std::function<T1> и std::function<T2> разными несовместимыми типами, когда T1 и T2 не совпадают. Это может сработать в вашей системе по чистой случайности, но я не верю, что вы можете с уверенностью предполагать, что это сработает.

person templatetypedef    schedule 13.06.2019
comment
Если это небезопасно, как я могу реализовать неконстантный вариант в терминах константного варианта в этой ситуации? - person WilliamKF; 14.06.2019
comment
У вас может быть неконстантный один проход в лямбде, который выполняет const_cast для удаления константности из своего аргумента, а затем, возможно, перенаправляет его в функцию, которая была передана в качестве аргумента? - person templatetypedef; 14.06.2019
comment
@WilliamKF Самое простое решение - не использовать std::function, а создать свой шаблон метода. Это было бы более эффективно, так как у std::function есть накладные расходы. - person Slava; 14.06.2019
comment
Использование лямбды кажется дорогостоящим. Этот код находится в критическом цикле, поэтому мне кажется, что я должен избавиться от std::function даже и применить шаблон функции к функции Processor. - person WilliamKF; 14.06.2019

reinterpret_cast здесь неопределенное поведение. const_cast не подходит, потому что const параметры шаблона нельзя отбросить.

Самый простой способ решить эту проблему - не надо. Избавьтесь от некоторых ссылок. Поменяйте местами, кто это реализует.

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 хранит вещи, которые совместимы с ним.

Если вы возьмете Foo&, вы можете вызвать с ним функцию, ожидающую Foo const&. Таким образом, вы можете хранить std::function<void(Foo const&)> в std::function<void(Foo&)>.

Заключение чего-либо в std::function может включать выделение. Так что вы можете захотеть найти высококачественный function_view<Sig>, который заменит ваше использование std::function.

В другом комментарии вы заявляете, что этот код находится в критическом цикле. Полное исключение std::function - хороший ход или, по крайней мере, уменьшение количества нажатий на стирание типов и передача ему как-то пакетов данных.

Инверсия констант / констант все еще уместна; мы реализуем константу в терминах изменяемой, а не изменяемой в терминах константы, потому что одна операция является ковариантной, а другая - контравариантной. См. здесь.

person Yakk - Adam Nevraumont    schedule 14.06.2019
comment
Теперь я использую лямбду вместо std::function, что привело к значительному ускорению. См. Соответствующий вопрос здесь. - person WilliamKF; 19.06.2019