Как мне указать указатель на перегруженную функцию?


137

Я хочу передать перегруженную функцию в std::for_each()алгоритм. Например,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

Я ожидаю, что компилятор разрешит f()тип итератора. По-видимому, он (GCC 4.1.2) этого не делает. Итак, как я могу указать, какой f()я хочу?


Ответы:


137

Вы можете использовать, static_cast<>()чтобы указать, что fиспользовать в соответствии с сигнатурой функции, подразумеваемой типом указателя функции:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

Или вы также можете сделать это:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

Если fэто функция-член, то вам нужно использовать mem_fun, или для вашего случая, использовать решение, представленное в этой статье доктора Добба .


1
Спасибо! У меня все еще есть проблема, хотя, вероятно, из-за того, что f()является членом класса (см. Отредактированный пример выше)
davka

9
@the_drow: Второй метод на самом деле намного безопаснее, если одна из перегрузок уходит, первый метод молча дает неопределенное поведение, а второй улавливает проблему во время компиляции.
Бен Фойгт

3
@BenVoigt Хм, я протестировал это на vs2010 и не смог найти случай, когда static_cast не уловил бы проблему во время компиляции. Он дал C2440 с «Ни одна из функций с таким именем в области видимости не соответствует целевому типу». Вы можете уточнить?
Натан Монтелеоне

5
@ Натан: Возможно, я думал об этом reinterpret_cast. Чаще всего я вижу броски в стиле C, используемые для этого. Мое правило состоит в том, что приведение указателей на функции опасно и не нужно (как показывает второй фрагмент кода, существует неявное преобразование).
Бен Фойгт

3
Для функций-членов:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

Лямбда на помощь! (примечание: требуется C ++ 11)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

Или используя decltype для параметра lambda:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

С полиморфными лямбдами (C ++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

Или устраните неоднозначность, удалив перегрузку (работает только для свободных функций):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}

Ура для лямбд! Действительно, отличное решение проблемы разрешения перегрузки. (Я тоже подумал об этом, но решил не включать его в свой ответ, чтобы не запачкать воду.)
aldo

Больше кода для того же результата. Я думаю, что это не то, для чего были сделаны лямбды.
Томаш Зато - Восстановить Монику

@ TomášZato Разница в том, что этот ответ работает, а принятый - нет (для примера, размещенного OP - вам также нужно использовать mem_fnи bind, кстати, также C ++ 11). Также, если мы хотим стать действительно педантичными, [&](char a){ return f(a); }это 28 символов, а static_cast<void (A::*)(char)>(&f)это 35 символов.
Milleniumbug

1
@ TomášZato Вот вы, coliru.stacked-crooked.com/a/1faad53c4de6c233 не знаете, как это сделать яснее
milleniumbug

18

Почему не работает

Я ожидаю, что компилятор будет разрешен f()по типу итератора. По-видимому, он (gcc 4.1.2) этого не делает.

Было бы здорово, если бы это было так! Тем не менее, for_eachэто шаблон функции, объявленный как:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

Шаблон вычета должен выбрать тип для UnaryFunctionв точке вызова. Но fне имеет определенного типа - это перегруженная функция, есть много fs с разными типами. В настоящее время нет способа for_eachпомочь процессу вывода шаблонов, указав, чего fон хочет, поэтому вывод шаблонов просто не удался. Для успешного вывода шаблона вам нужно проделать дополнительную работу на сайте вызовов.

Общее решение для его устранения

Прыгнул сюда через несколько лет, а C ++ 14 позже. Вместо того, чтобы использовать static_cast(который позволил бы вывести шаблон успешно, путем «исправления», который fмы хотим использовать, но требует, чтобы вы вручную сделали разрешение перегрузки, чтобы «исправить» правильное), мы хотим заставить компилятор работать на нас. Мы хотим fпривести некоторые аргументы. В наиболее общем виде это:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

Это много, чтобы напечатать, но такого рода проблемы возникают раздражающе часто, поэтому мы можем просто обернуть это в макрос (вздох):

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

а затем просто используйте его:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

Это будет делать именно то, что вы хотели, чтобы компилятор делал - выполнял разрешение перегрузки для самого имени fи просто делал правильные вещи. Это будет работать независимо от того, fявляется ли свободная функция или функция-член.


7

Не ответить на ваш вопрос, но я единственный, кто находит

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

и проще, и короче, чем for_eachальтернатива, предложенная in silico в этом случае?


2
возможно, но это скучно :) также, если я хочу использовать итератор, чтобы избежать оператора [], это становится длиннее ...
Давка

3
@Davka Скучно то, что мы хотим. Кроме того, итераторы обычно не быстрее (могут быть медленнее), чем использование op [, если это ваша задача.

7
Алгоритмы должны быть предпочтительнее для циклов for, поскольку они менее подвержены ошибкам и могут иметь больше возможностей для оптимизации. Где-то есть статья об этом ... вот она: drdobbs.com/184401446
AshleysBrain

5
@Ashley Пока я не увижу какую-то объективную статистику о "менее подверженной ошибкам", я не вижу необходимости в это верить И Мейерс в этой статье, кажется, говорит о циклах, которые используют итераторы - я говорю об эффективности циклов, которые не используют итераторы - мои собственные тесты имеют тенденцию предполагать, что они немного быстрее при оптимизации - конечно, не медленнее.

1
Вот и я, я тоже нахожу ваше решение намного лучше.
Петер - Восстановить Монику

5

Проблема здесь, кажется, не в разрешении перегрузки, а в самом деле выводе параметров шаблона . Хотя превосходный ответ @In silico решит проблему неоднозначной перегрузки в целом, при работе с std::for_each(или подобным) лучшим решением будет явное указание параметров его шаблона :

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}

4

Если вы не возражаете против использования C ++ 11, вот умный помощник, который похож (но менее уродлив, чем) на статическое приведение:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(Работает для функций-членов; должно быть очевидно, как изменить его для работы с автономными функциями, и вы должны быть в состоянии предоставить обе версии, и компилятор выберет правильную для вас.)

Спасибо Миро Кнейпу за предложение: см. Также https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .


Проблема OP в том, что невозможно передать перегруженное имя в шаблон функции, а ваше решение заключается в передаче перегруженного имени в шаблон функции? Это точно такая же проблема.
Барри

1
@ Барри Не та же проблема. Вывод аргумента шаблона в этом случае успешен. Это работает (с небольшими изменениями).
Окталист

@Oktalist Потому что вы предоставляете R, это не выводится. Там также нет упоминания об этом в этом ответе.
Барри

1
@ Барри, я не даю R, я даю Args. Rи Tвыводятся. Это правда, что ответ может быть улучшен. (В Tмоем примере это не так, потому что это не указатель на член, потому что это не сработает std::for_each.)
Окталист
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.