Как мне указать указатель на перегруженную функцию? - PullRequest
123 голосов
/ 31 мая 2010

Я хочу передать перегруженную функцию алгоритму 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() я хочу?

Ответы [ 6 ]

125 голосов
/ 31 мая 2010

Вы можете использовать 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, или для вашего случая используйте решение, представленное в статье этого доктора Добба .

24 голосов
/ 18 февраля 2014

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

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

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

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);
}
15 голосов
/ 22 апреля 2016

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

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

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

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

Для вывода шаблона необходимо выбрать тип для UnaryFunction в точке вызова. Но f не имеет определенного типа - это перегруженная функция, существует множество f с разными типами. Для 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 свободной функцией или функцией-членом.

4 голосов
/ 30 марта 2015

Если вы не возражаете против использования 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.

4 голосов
/ 18 февраля 2014

Проблема здесь, кажется, не в разрешении перегрузки , а на самом деле удержании параметра шаблона . В то время как отличный ответ от @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 голосов
/ 31 мая 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...