Это похоже на то, что нужно часто спрашивать и отвечать, но мое поисковое испытание подвело меня.
Я пишу функцию, которую я хочу принять общий вызываемый объект некоторого вида (в том числе голые функции, объект функтора, свернутый вручную, bind
, или же std::function
), а затем вызвать его в недрах алгоритма (т. е. лямбда).
Функция в настоящее время объявлена так:
template<typename T, typename F>
size_t do_something(const T& a, const F& f)
{
T internal_value(a);
// do some complicated things
// loop {
// loop {
f(static_cast<const T&>(internal_value), other_stuff);
// do some more things
// }
// }
return 42;
}
Я принимаю функтор по ссылке, потому что хочу гарантировать, что он не будет скопирован при входе в функцию, и, таким образом, фактически вызывается тот же экземпляр объекта. И это константная ссылка, потому что это единственный способ принимать временные объекты (что часто встречается при использовании свернутых вручную функторов или bind
).
Но для этого необходимо, чтобы функтор operator()
как постоянный Я не хочу требовать этого; Я хочу, чтобы он мог принять оба.
Я знаю, что могу объявить две копии этого метода, один, который принимает его как const, а другой как неконстантный, чтобы охватить оба случая. Но я не хочу этого делать, поскольку в комментариях скрывается довольно много кода, который я не хочу дублировать (включая некоторые конструкции цикла, поэтому я не могу извлечь их во вторичный метод, не перенеся проблему) ,
Я также знаю, что я мог бы обмануть и const_cast
функтор неконстантен до того, как я его вызову, но это кажется потенциально опасным (и, в частности, вызовет неправильный метод, если функтор намеренно реализует операторы вызова как константного, так и неконстантного вызова).
Я рассматривал принятие функтора как std::function
/boost::function
, но это похоже на тяжелое решение того, что должно быть простой проблемой. (Особенно в случае, когда функтор должен ничего не делать.)
Есть ли «правильный» способ удовлетворить эти требования, кроме дублирования алгоритма?
[Примечание: я бы предпочел решение, которое не требует C ++ 11, хотя меня также интересуют ответы C ++ 11, так как я использую аналогичные конструкции в проектах для обоих языков.]Вы пробовали слой пересылки, чтобы сделать вывод классификатора? Пусть компилятор выполнить дублирование алгоритма с помощью обычного механизма создания шаблонов.
template<typename T, typename F>
size_t do_something_impl(const T& a, F& f)
{
T internal_value(a);
const T& c_iv = interval_value;
// do some complicated things
// loop {
// loop {
f(c_iv, other_stuff);
// do some more things
// }
// }
return 42;
}
template<typename T, typename F>
size_t do_something(const T& a, F& f)
{
return do_something_impl<T,F>(a, f);
}
template<typename T, typename F>
size_t do_something(const T& a, const F& f)
{
return do_something_impl<T,const F>(a, f);
}
Демо-версия: http://ideone.com/owj6oB
Оболочка должна быть полностью встроенной и вообще не иметь затрат времени выполнения, за исключением того факта, что вы можете получить больше экземпляров шаблона (и, следовательно, больший размер кода), хотя это произойдет, только если для типов без operator()() const
где проходят как константные (или временные), так и неконстантные функторы.
В комментарии к другому ответу ФП уточнил / изменил требования на…
«Я согласен с требованием, если функтор будет передан как временный
тогда он должен иметь оператор () const. Я просто не хочу ограничивать это
к тому, что, если функтор не передается в качестве временного (и
тоже не const non-временная, конечно) тогда разрешено иметь
неконстантный оператор (), и это будет называться
Это то не проблема вообще: просто предоставьте перегрузку, которая принимает временную.
Существует несколько способов отличить исходную базовую реализацию, например, в C ++ 11 дополнительный аргумент шаблона по умолчанию, а в C ++ 03 дополнительный аргумент обычной функции по умолчанию.
Но самое ясное — ИМХО просто дать ему другое имя и затем предоставить перегруженную оболочку:
template<typename T, typename F>
size_t do_something_impl( T const& a, F& f)
{
T internal_value(a);
// do some complicated things
// loop {
// loop {
f(static_cast<const T&>(internal_value), other_stuff);
// do some more things
// }
// }
return 42;
}
template<typename T, typename F>
size_t do_something( T const& a, F const& f)
{ return do_something_impl( a, f ); }
template<typename T, typename F>
size_t do_something( T const& a, F& f)
{ return do_something_impl( a, f ); }
Примечание: нет необходимости указывать do_something_impl
конкретизация, так как это выводится из аргументов lvalue.
Основная особенность этого подхода заключается в том, что он поддерживает более простые вызовы, за счет того, что он не поддерживает временный аргумент, если он не имеетconst
operator()
,
Ваша главная цель — избежать копирования функтора и принять временный аргумент в качестве фактического.
В C ++ 11 вы можете просто использовать ссылку rvalue, &&
Для C ++ 03 проблемой является временный экземпляр функтора в качестве фактического аргумента, где этот функтор имеетconst
operator()
,
Одним из решений является передача бремени программисту клиентского кода, например
требовать, чтобы фактический аргумент был lvalue, а не временным, или же
требовать явного указания, что аргумент является временным, а затем принять его как ссылку на const
и использовать const_cast
,
Пример:
template<typename T, typename F>
size_t do_something( T const& a, F& f)
{
T internal_value(a);
// do some complicated things
// loop {
// loop {
f(static_cast<const T&>(internal_value), other_stuff);
// do some more things
// }
// }
return 42;
}
enum With_temp { with_temp };
template<typename T, typename F>
size_t do_something( T const& a, With_temp, F const& f )
{
return do_something( a, const_cast<F&>( f ) );
}
Если это необходимо, чтобы напрямую поддержать временные const
Например, чтобы облегчить жизнь программисту клиентского кода и в этом редком случае, одним из решений будет просто добавить дополнительную перегрузку:
enum With_const_temp { with_const_temp };
template<typename T, typename F>
size_t do_something( T const& a, With_const_temp, F const& f )
{
return do_something( a, f );
}
Спасибо Стиву Джессопу и Бену Фойгту за обсуждение этого дела.
Альтернативный и гораздо более общий путь C ++ 03 — предоставить следующие две маленькие функции:
template< class Type >
Type const& ref( Type const& v ) { return v; }
template< class Type >
Type& non_const_ref( Type const& v ) { return const_cast<T&>( v ); }
затем do_something
, как указано выше в этом ответе, можно назвать как …
do_something( a, ref( MyFunctor() ) );
do_something( a, non_const_ref( MyFunctor() ) );
Почему я не подумал об этом сразу, несмотря на то, что использовал это решение для других вещей, таких как построение строк: легко создавать сложности, а проще упрощать! 🙂