Làm cách nào để chỉ định một con trỏ tới hàm quá tải?


137

Tôi muốn chuyển một hàm quá tải cho std::for_each()thuật toán. Ví dụ,

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

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

Tôi mong muốn trình biên dịch giải quyết f()theo kiểu lặp. Rõ ràng, nó (GCC 4.1.2) không làm điều đó. Vì vậy, làm thế nào tôi có thể chỉ định mà f()tôi muốn?


Câu trả lời:


137

Bạn có thể sử dụng static_cast<>()để chỉ định fsử dụng theo chữ ký hàm được ngụ ý bởi loại con trỏ hàm:

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

Hoặc, bạn cũng có thể làm điều này:

// 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

Nếu flà một hàm thành viên, thì bạn cần sử dụng mem_funhoặc cho trường hợp của bạn, sử dụng giải pháp được trình bày trong bài viết của Tiến sĩ Dobb này .


1
cảm ơn! Tuy nhiên, tôi vẫn có một vấn đề có lẽ là do thực tế f()là thành viên của một lớp (xem ví dụ đã chỉnh sửa ở trên)
davka

9
@the_drow: Phương thức thứ hai thực sự an toàn hơn nhiều, nếu một trong những tình trạng quá tải biến mất, phương thức thứ nhất âm thầm đưa ra hành vi không xác định, trong khi phương thức thứ hai bắt được vấn đề tại thời điểm biên dịch.
Ben Voigt

3
@BenVoigt Hmm, tôi đã thử nghiệm điều này trên vs2010 và không thể tìm thấy trường hợp nào static_cast không bắt gặp sự cố tại thời điểm biên dịch. Nó đã đưa ra một C2440 với "Không có chức năng nào có tên này trong phạm vi khớp với loại mục tiêu". Bạn có thể làm rõ?
Nathan Monteleone

5
@Nathan: Có thể tôi đã nghĩ đến reinterpret_cast. Thông thường tôi thấy các diễn viên kiểu C được sử dụng cho việc này. Quy tắc của tôi chỉ là các phôi trên các con trỏ hàm là nguy hiểm và không cần thiết (như đoạn mã thứ hai cho thấy, một chuyển đổi ngầm tồn tại).
Ben Voigt

3
Đối với các chức năng thành viên:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

Lambdas để giải cứu! (lưu ý: C ++ 11 bắt buộc)

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

Hoặc sử dụng dectype cho tham số lambda:

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

Với lambdas đa hình (C ++ 14):

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

Hoặc định hướng bằng cách loại bỏ quá tải (chỉ hoạt động cho các chức năng miễn phí):

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

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

Chiến thắng cho lambdas! Thật vậy, một giải pháp tuyệt vời cho vấn đề giải quyết quá tải. (. Tôi nghĩ điều này cũng có, nhưng quyết định rời nó ra khỏi câu trả lời của tôi để không lầy lội nước)
aldo

Nhiều mã hơn cho cùng một kết quả. Tôi nghĩ đó không phải là những gì lambdas được tạo ra.
Tomáš Zato - Phục hồi Monica

@ TomášZato Sự khác biệt là câu trả lời này hoạt động và câu trả lời không được chấp nhận (ví dụ được đăng bởi OP - bạn cũng cần sử dụng mem_fnbind, trong đó, BTW. Cũng là C ++ 11). Ngoài ra, nếu chúng ta muốn thực sự có phạm vi [&](char a){ return f(a); }là 28 ký tự và static_cast<void (A::*)(char)>(&f)là 35 ký tự.
milleniumorms

1
@ TomášZato Ở đó bạn đi coliru.stacked-crooking.com/a/1faad53c4de6c233 không chắc chắn làm thế nào để làm cho điều này rõ ràng hơn
milleniumorms

18

Tại sao nó không hoạt động

Tôi mong muốn trình biên dịch giải quyết f()theo kiểu lặp. Rõ ràng, nó (gcc 4.1.2) không làm điều đó.

Sẽ thật tuyệt nếu đó là trường hợp! Tuy nhiên, for_eachlà một mẫu hàm, được khai báo là:

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

Khấu trừ mẫu cần chọn một loại cho UnaryFunctionđiểm tại của cuộc gọi. Nhưng fkhông có một loại cụ thể - đó là một hàm quá tải, có nhiều floại với mỗi loại khác nhau. Không có cách nào hiện tại for_eachđể hỗ trợ quá trình khấu trừ mẫu bằng cách nêu rõ fnó muốn, vì vậy việc khấu trừ mẫu đơn giản là thất bại. Để việc khấu trừ mẫu thành công, bạn cần thực hiện nhiều công việc hơn trên trang web cuộc gọi.

Giải pháp chung để sửa nó

Nhảy vào đây một vài năm và C ++ 14 sau đó. Thay vì sử dụng static_cast(sẽ cho phép khấu trừ mẫu thành công bằng cách "sửa" mà fchúng tôi muốn sử dụng, nhưng yêu cầu bạn phải tự giải quyết quá tải để "sửa" chính xác), chúng tôi muốn làm cho trình biên dịch hoạt động cho chúng tôi. Chúng tôi muốn gọi fvề một số đối số. Theo cách chung nhất có thể, đó là:

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

Đó là rất nhiều loại, nhưng loại vấn đề này xuất hiện thường xuyên một cách khó chịu, vì vậy chúng ta chỉ có thể gói nó trong một macro (thở dài):

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

và sau đó chỉ cần sử dụng nó:

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

Điều này sẽ làm chính xác những gì bạn muốn trình biên dịch đã làm - thực hiện giải quyết quá tải trên fchính tên đó và chỉ cần làm đúng. Điều này sẽ hoạt động bất kể flà chức năng miễn phí hay chức năng thành viên.


7

Không trả lời câu hỏi của bạn, nhưng tôi có phải là người duy nhất tìm thấy

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

cả đơn giản và ngắn hơn so với giải for_eachpháp thay thế được đề xuất bởi silico trong trường hợp này?


2
có lẽ, nhưng thật nhàm chán :) cũng vậy, nếu tôi muốn sử dụng iterator để tránh toán tử [], điều này sẽ trở nên dài hơn ...
davka

3
@Davka Chán là những gì chúng ta muốn. Ngoài ra, các trình vòng lặp thường không nhanh hơn (có thể chậm hơn) so với sử dụng op [, nếu đó là mối quan tâm của bạn.

7
Các thuật toán nên được ưu tiên cho các vòng lặp, vì chúng ít bị lỗi hơn và có thể có cơ hội tối ưu hóa tốt hơn. Có một bài viết về một nơi nào đó ... đây là: drdobbs.com/184401446
AshleyBrain

5
@Ashley Cho đến khi tôi thấy một số thống kê khách quan về "ít bị lỗi" tôi thấy không cần phải tin vào điều đó. Và Meyers trong bài viết dường như nói về các vòng lặp sử dụng các vòng lặp - Tôi đang nói về hiệu quả của các vòng lặp mà KHÔNG sử dụng các vòng lặp - các điểm chuẩn của riêng tôi có xu hướng gợi ý rằng các vòng lặp này nhanh hơn một chút khi được tối ưu hóa - chắc chắn không chậm hơn.

1
Tôi đây, tôi cũng thấy giải pháp của bạn tốt hơn nhiều.
peterh - Phục hồi Monica

5

Vấn đề ở đây dường như không phải là quá tải độ phân giải mà trên thực tế là khấu trừ tham số mẫu . Mặc dù câu trả lời tuyệt vời từ @In silico sẽ giải quyết vấn đề quá tải mơ hồ nói chung, có vẻ như cách khắc phục tốt nhất khi xử lý std::for_each(hoặc tương tự) là chỉ định rõ ràng các tham số mẫu của nó :

// 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

Nếu bạn không phiền khi sử dụng C ++ 11, thì đây là một người trợ giúp thông minh tương tự (nhưng ít xấu hơn) diễn viên tĩnh:

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

(Hoạt động cho các chức năng thành viên; nên rõ ràng cách sửa đổi nó để hoạt động cho các chức năng tự do và bạn sẽ có thể cung cấp cả hai phiên bản và trình biên dịch sẽ chọn đúng phiên bản cho bạn.)

Cảm ơn Miro Knejp đã gợi ý: xem thêm https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .


Vấn đề của OP là không thể chuyển một tên quá tải vào một mẫu hàm và giải pháp của bạn liên quan đến việc chuyển một tên quá tải vào một mẫu hàm? Đây chính xác là cùng một vấn đề.
Barry

1
@Barry Không phải vấn đề tương tự. Mẫu suy luận đối số thành công trong trường hợp này. Nó hoạt động (với một vài điều chỉnh nhỏ).
Oktalist

@Oktalist Bởi vì bạn đang cung cấp R, nó không bị suy diễn. Cũng không có đề cập đến điều đó trong câu trả lời này.
Barry

1
@Barry Tôi không cung cấp R, tôi đang cung cấp Args. RTđược suy luận. Đó là sự thật câu trả lời có thể được cải thiện. (Không có Ttrong ví dụ của tôi mặc dù, bởi vì nó không phải là một thành viên con trỏ-to-, bởi vì đó sẽ không làm việc với std::for_each.)
Oktalist
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.