Як вказати вказівник на перевантажену функцію?


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
@Nathan: Можливо, я думав reinterpret_cast. Найчастіше я бачу касти в стилі С, використовувані для цього. Моє правило полягає лише в тому, що касти на покажчики функцій небезпечні та непотрібні (як показує другий фрагмент коду, існує неявна конверсія).
Бен Войт

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 для параметра лямбда:

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 Різниця полягає в тому, що ця відповідь спрацьовує, а прийнята - ні (наприклад, опублікована ОП - вам також потрібно використовувати mem_fnі bind, що, BTW., Також C ++ 11). Крім того, якщо ми хочемо отримати дійсно педантичний [&](char a){ return f(a); }- це 28 символів, а static_cast<void (A::*)(char)>(&f)це 35 символів.
тисячоліття,

1
@ TomášZato Там ви відправляєтесь coliru.stacked-crooked.com/a/1faad53c4de6c233 не знаєте, як зробити це більш зрозумілим
тисячоліття

18

Чому це не працює

Я очікую, що компілятор вирішить f()за типом ітератора. Мабуть, це (gcc 4.1.2) не робить цього.

Було б чудово, якби це було так! Однак for_eachце шаблон функції, оголошений як:

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

Для виведення шаблону потрібно вибрати тип для UnaryFunctionв точці виклику. Але fне має конкретного типу - це перевантажена функція, їх багато fз різними типами. Не існує поточного способу for_eachсприяти процесу виведення шаблону, заявивши, що fвін хоче, тому виведення шаблону просто не вдається. Для того, щоб виведення шаблону вдалося, вам потрібно зробити більше роботи на сайті виклику.

Загальне рішення для його фіксації

Стрибки сюди через кілька років, а С ++ 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альтернатива, запропонована в силіконі в цьому випадку?


2
певно, але це нудно :) також, якщо я хочу використовувати ітератор, щоб уникнути оператора [], це стає довше ...
davka

3
@Davka Boring - це те, що ми хочемо. Крім того, ітератори, як правило, не швидші (можуть бути повільнішими), ніж використання op [, якщо це стосується вас.

7
Алгоритми слід віддавати перевагу петлям, оскільки вони менш схильні до помилок і можуть мати кращі можливості для оптимізації. Десь про це є стаття ... ось вона: 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
@Barry Не та сама проблема. У цьому випадку виведення аргументу шаблону вдається. Це працює (з декількома незначними налаштуваннями).
Окталіст

@Oktalist Оскільки ви надаєте R, це не виводиться. У цій відповіді про це також не згадується.
Баррі

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