ฉันจะระบุตัวชี้ไปยังฟังก์ชันที่โอเวอร์โหลดได้อย่างไร


139

ฉันต้องการส่งผ่านฟังก์ชันที่มากเกินไปไปยัง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()ฉันต้องการได้อย่างไร?


คำตอบ:


139

คุณสามารถใช้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หรือสำหรับกรณีของคุณใช้วิธีการแก้ปัญหาที่นำเสนอในบทความนี้ดร. Dobb ของ


1
ขอบคุณ! ฉันยังคงมีปัญหาอยู่อาจเนื่องมาจากการที่f()เป็นสมาชิกของชั้นเรียน (ดูตัวอย่างที่แก้ไขด้านบน)
davka

9
@the_drow: วิธีที่สองปลอดภัยกว่ามากหากการโอเวอร์โหลดอย่างใดอย่างหนึ่งหายไปวิธีแรกจะให้พฤติกรรมที่ไม่ได้กำหนดอย่างเงียบ ๆ ในขณะที่วิธีที่สองจะแก้ไขปัญหาในเวลาคอมไพล์
Ben Voigt

3
@BenVoigt อืมฉันทดสอบสิ่งนี้ใน vs2010 และไม่พบกรณีที่ static_cast ไม่สามารถจับปัญหาได้ในเวลาคอมไพล์ มันให้ C2440 พร้อมกับ "ไม่มีฟังก์ชันที่มีชื่อนี้ในขอบเขตที่ตรงกับประเภทเป้าหมาย" คุณสามารถชี้แจง?
Nathan Monteleone

5
@Nathan: reinterpret_castมันเป็นไปได้ที่ผมคิดว่า บ่อยที่สุดที่ฉันเห็นนักแสดงสไตล์ C ใช้สำหรับสิ่งนี้ กฎของฉันคือการร่ายในตัวชี้ฟังก์ชันเป็นสิ่งที่อันตรายและไม่จำเป็น (ตามที่ข้อมูลโค้ดที่สองแสดงให้เห็นว่ามีการแปลงโดยนัยอยู่)
Ben Voigt

3
สำหรับการทำงานของสมาชิก:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

Lambdas เพื่อช่วยเหลือ! (หมายเหตุ: ต้องใช้ 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); });

ด้วย polymorphic lambdas (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);
}

ไชโยสำหรับ lambdas! อันที่จริงวิธีแก้ปัญหาที่ดีเยี่ยมสำหรับการแก้ปัญหาการโอเวอร์โหลด (ฉันคิดถึงเรื่องนี้เช่นกัน แต่ก็ตัดสินใจที่จะทิ้งคำตอบของฉันไว้เพื่อไม่ให้น้ำเป็นโคลน)
aldo

1
รหัสเพิ่มเติมสำหรับผลลัพธ์เดียวกัน ฉันคิดว่านั่นไม่ใช่สิ่งที่แลมด้าถูกสร้างขึ้นมาเพื่อ
Tomáš Zato - คืนสถานะ Monica

@ TomášZatoความแตกต่างคือคำตอบนี้ใช้ได้ผลและคำตอบที่ยอมรับไม่ได้ (สำหรับตัวอย่างที่โพสต์โดย OP - คุณต้องใช้ด้วยmem_fnและbindซึ่ง BTW ก็เป็น 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

19

ทำไมไม่ทำงาน

ฉันคาดหวังว่าคอมไพลเลอร์จะแก้ไข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 ต่อมา แทนที่จะใช้ a 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
ฉันอยู่ที่นี่ฉันยังพบวิธีแก้ปัญหาของคุณดีขึ้นมาก
peterh - คืนสถานะ Monica

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

(ใช้งานได้กับฟังก์ชันสมาชิกควรมีความชัดเจนว่าจะแก้ไขอย่างไรให้ทำงานกับฟังก์ชันอิสระและคุณควรจะสามารถจัดหาทั้งสองเวอร์ชันได้และคอมไพเลอร์จะเลือกเวอร์ชันที่เหมาะสมสำหรับคุณ)

ขอบคุณ Miro Knejp ที่แนะนำ: ดูhttps://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4Jด้วย


ปัญหาของ OP คือไม่สามารถส่งผ่านชื่อที่มากเกินไปไปยังเทมเพลตฟังก์ชันได้และโซลูชันของคุณเกี่ยวข้องกับการส่งชื่อที่มากเกินไปไปยังเทมเพลตฟังก์ชันหรือไม่? นี่เป็นเพียงปัญหาเดียวกัน
Barry

1
@ แบร์รี่ไม่ใช่ปัญหาเดียวกัน การหักอาร์กิวเมนต์แม่แบบทำได้สำเร็จในกรณีนี้ มันใช้งานได้ (ด้วยการปรับแต่งเล็กน้อยเล็กน้อย)
Oktalist

@Oktalist เนื่องจากคุณกำลังให้บริการRจึงไม่ได้อนุมาน นอกจากนี้ยังไม่มีการกล่าวถึงในคำตอบนี้
Barry

1
@Barry ฉันไม่ให้ฉันให้R และอนุมานได้ เป็นเรื่องจริงที่คำตอบสามารถปรับปรุงได้ (ไม่มีในตัวอย่างของฉันเพราะมันไม่ใช่ตัวชี้ไปที่สมาชิกเพราะมันใช้ไม่ได้)ArgsRTTstd::for_each
Oktalist
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.