ฉันจะลบการทำสำเนารหัสระหว่างฟังก์ชั่นสมาชิกแบบ const และ non-const ที่คล้ายกันได้อย่างไร


242

สมมติว่าฉันมีสิ่งต่อไปนี้class Xที่ฉันต้องการคืนการเข้าถึงสมาชิกภายใน:

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

สมาชิกทั้งสองฟังก์ชั่นX::Z()และX::Z() constมีรหัสที่เหมือนกันภายในวงเล็บปีกกา นี่คือรหัสที่ซ้ำกันและอาจทำให้เกิดปัญหาการบำรุงรักษาสำหรับการทำงานที่ยาวนานกับตรรกะที่ซับซ้อน

มีวิธีการหลีกเลี่ยงการทำซ้ำรหัสนี้หรือไม่?


ในตัวอย่างนี้ฉันจะคืนค่าในกรณี const เพื่อให้คุณไม่สามารถ refactoring ด้านล่าง int Z () const {return z; }
ราคาแมตต์

1
สำหรับประเภทพื้นฐานคุณถูกต้องจริงๆ! ตัวอย่างแรกของฉันไม่ดีมาก สมมติว่าเราส่งคืนคลาสบางส่วนแทน (ฉันได้อัปเดตคำถามเพื่อสะท้อนสิ่งนี้)
Kevin

คำตอบ:


189

สำหรับคำอธิบายโดยละเอียดโปรดดูหัวข้อ "หลีกเลี่ยงการทำซ้ำในconstและconstฟังก์ชั่นที่ไม่ใช่สมาชิก" ในหน้า 23, ในรายการ 3 "ใช้constเมื่อใดก็ตามที่เป็นไปได้" ในC ++ ที่มีประสิทธิภาพ , 3d edโดย Scott Meyers, ISBN-13: 9780321334879

ข้อความแสดงแทน

นี่คือวิธีแก้ปัญหาของ Meyers (ลดความซับซ้อน):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

การปลดเปลื้องสองครั้งและการเรียกใช้ฟังก์ชันอาจน่าเกลียด แต่ก็ถูกต้อง เมเยอร์สมีคำอธิบายโดยละเอียดว่าทำไม


45
ไม่มีใครถูกไล่ออกจากการติดตาม Scott Meyers :-)
Steve Jessop

11
witkamp นั้นถูกต้องแล้วโดยทั่วไปมันไม่ดีที่จะใช้ const_cast นี่เป็นกรณีเฉพาะที่ไม่ได้เป็นอย่างที่เมเยอร์สอธิบาย @Adam: ROM => const ไม่เป็นไร const == ROM นั้นเป็นเรื่องไร้สาระอย่างเห็นได้ชัดเพราะทุกคนสามารถส่งไม่ใช่วิลถึง const วิลลี่ - เนลลี่: มันเทียบเท่ากับเพียงแค่เลือกที่จะไม่แก้ไขอะไรบางอย่าง
Steve Jessop

44
โดยทั่วไปฉันขอแนะนำให้ใช้ const_cast แทน static_cast เพื่อเพิ่ม const เนื่องจากจะป้องกันไม่ให้คุณเปลี่ยนประเภทโดยไม่ตั้งใจ
เกร็กโรเจอร์ส

6
@HelloGoodbye: ผมคิดว่าเมเยอร์สถือว่าจำนวนเล็กน้อยของหน่วยสืบราชการลับจากนักออกแบบของอินเตอร์เฟซชั้นเรียน หากget()constส่งคืนบางสิ่งที่ถูกกำหนดเป็นวัตถุ const แล้วไม่ควรมีรุ่นที่ไม่ใช่ const get()เลย ที่จริงแล้วความคิดของฉันเกี่ยวกับเรื่องนี้เปลี่ยนแปลงไปตามเวลา: โซลูชันเทมเพลตเป็นวิธีเดียวที่จะหลีกเลี่ยงการทำซ้ำและรับการตรวจสอบความถูกต้องของคอมไพเลอร์ดังนั้นโดยส่วนตัวแล้วฉันจะไม่ใช้อีกต่อไปconst_castเพื่อหลีกเลี่ยงรหัสซ้ำ รหัสที่ถูกทำสำเนาในเทมเพลตของฟังก์ชัน
Steve Jessop

7
ต่อไปนี้สองแม่ช่วยอย่างมากกับการอ่านของการแก้ปัญหานี้: และtemplate<typename T> const T& constant(T& _) { return const_cast<const T&>(_); } template<typename T> T& variable(const T& _) { return const_cast<T&>(_); }จากนั้นคุณสามารถทำได้:return variable(constant(*this).get());
Casey Rodarmor

64

ใช่เป็นไปได้ที่จะหลีกเลี่ยงการทำซ้ำรหัส คุณจำเป็นต้องใช้ฟังก์ชั่นสมาชิก const ที่จะมีตรรกะและมีฟังก์ชั่นที่ไม่ใช่สมาชิก const เรียกฟังก์ชั่นสมาชิก const และหล่อค่าตอบแทนกลับไปอ้างอิง non-const (หรือตัวชี้ถ้าฟังก์ชั่นส่งกลับตัวชี้):

class X
{
   std::vector<Z> vecZ;

public:
   const Z& z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.z(index) );
   }
 #endif
};

หมายเหตุ:มันเป็นสิ่งสำคัญที่คุณไม่ได้ใส่ตรรกะในฟังก์ชั่นที่ไม่ใช่ const และมีฟังก์ชั่น const โทรฟังก์ชั่นที่ไม่ใช่ const - มันอาจส่งผลให้พฤติกรรมที่ไม่ได้กำหนด เหตุผลก็คืออินสแตนซ์ของคลาสคงที่ได้รับการคาสต์เป็นอินสแตนซ์ที่ไม่คงที่ ฟังก์ชันสมาชิกที่ไม่ใช่ const อาจแก้ไขคลาสโดยไม่ตั้งใจซึ่งสถานะมาตรฐาน C ++ จะส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด


3
ว้าว ... มันน่ากลัว คุณเพียงแค่เพิ่มปริมาณของรหัสที่ลดลงชัดเจนและเพิ่มสองเหม็น const_cast <> s บางทีคุณอาจมีตัวอย่างอยู่ในใจว่าสิ่งนี้เหมาะสมจริงหรือ
Shog9

14
เฮ้อย่าทำแบบนี้! มันอาจจะน่าเกลียด แต่สกอตต์เมเยอร์สตามวิธีที่ถูกต้อง (เกือบ) ดูC ++ ที่มีประสิทธิภาพ , 3d ed, รายการ 3 ภายใต้หัวข้อ "หลีกเลี่ยงการทำซ้ำในฟังก์ชั่นสมาชิก const และไม่ใช่ค่าใช้จ่าย
jwfearn

17
ในขณะที่ฉันเข้าใจว่าโซลูชันอาจน่าเกลียดลองนึกดูว่าโค้ดที่กำหนดว่าจะส่งคืนนั้นยาว 50 บรรทัดอย่างไร การทำซ้ำจึงเป็นสิ่งที่ไม่พึงประสงค์อย่างมากโดยเฉพาะเมื่อคุณต้องคำนึงถึงรหัสอีกครั้ง ฉันเคยเจออาชีพนี้มาหลายครั้งแล้ว
Kevin

8
ความแตกต่างระหว่างสิ่งนี้กับ Meyers คือ Meyers มี static_cast <const X &> (* this) const_cast ใช้สำหรับลบ const ไม่ใช่เพิ่ม
Steve Jessop

8
@VioletGiraffe เรารู้ว่าวัตถุไม่ได้ถูกสร้างขึ้นเป็น const เนื่องจากเป็นสมาชิกที่ไม่ใช่สมาชิกของวัตถุที่ไม่ใช่ const ซึ่งเรารู้เพราะเราอยู่ในวิธีที่ไม่ใช่ const ของวัตถุดังกล่าว คอมไพเลอร์ไม่ได้ทำการอนุมานมันตามกฎอนุรักษ์นิยม ทำไมคุณถึงคิดว่า const_cast มีอยู่ถ้าไม่ใช่สำหรับสถานการณ์แบบนี้?
Caleth

47

C ++ 17 ได้อัปเดตคำตอบที่ดีที่สุดสำหรับคำถามนี้:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast<T &>(std::as_const(*this).f());
}

นี่คือข้อดีที่:

  • เห็นได้ชัดว่าเกิดอะไรขึ้น
  • มีรหัสค่าใช้จ่ายน้อยที่สุด - มันพอดีในบรรทัดเดียว
  • เป็นเรื่องยากที่จะเข้าใจผิด (สามารถทิ้งไปvolatileโดยบังเอิญ แต่volatileเป็นผู้คัดเลือกที่หายาก)

หากคุณต้องการที่จะไปเส้นทางการหักเงินเต็มรูปแบบนั้นสามารถทำได้โดยมีฟังก์ชั่นผู้ช่วย

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template<typename T>
void as_mutable(T const &&) = delete;

ตอนนี้คุณไม่สามารถแม้แต่จะสับสนvolatileและการใช้งานดูเหมือนว่า

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}

โปรดทราบว่า "as_mutable" กับ const rvalue เกินที่ถูกลบ (ซึ่งเป็นที่นิยมโดยทั่วไป) ป้องกันไม่ให้ตัวอย่างสุดท้ายจากการทำงานถ้าf()ผลตอบแทนแทนT T&
Max Truxa

1
@ MaxTruxa: ใช่และนี่คือสิ่งที่ดี ถ้ามันรวบรวมเพียงเราจะมีการอ้างอิงห้อย ในกรณีที่f()ส่งคืนTเราไม่ต้องการมีสองเกินพิกัดconstรุ่นเดียวเพียงพอ
เดวิดสโตน

จริงมากฉันขอโทษสำหรับการผายลมเต็มรูปแบบของฉันเมื่อวานนี้ไม่มีความคิดว่าฉันคิดอะไรเมื่อฉันเขียนความคิดเห็นนั้น shared_ptrฉันถูกมองที่เป็น const / ผันแปรคู่ทะเยอทะยานกลับ ดังนั้นสิ่งที่ฉันต้องการจริงเป็นสิ่งที่ต้องการas_mutable_ptrซึ่งมีลักษณะเกือบจะเหมือนกับas_mutableข้างต้นยกเว้นว่าจะใช้เวลาและผลตอบแทนshared_ptrและการใช้งานแทนstd::const_pointer_cast const_cast
Max Truxa

1
หากวิธีการส่งกลับT const*แล้วสิ่งนี้จะผูกT const* const&&มากกว่าผูกพันT const* const&(อย่างน้อยในการทดสอบของฉันมัน) ฉันต้องเพิ่มเกินพิกัดสำหรับT const*เป็นประเภทอาร์กิวเมนต์สำหรับวิธีการส่งกลับตัวชี้
monkey0506

2
@ monkey0506: ฉันได้อัปเดตคำตอบของฉันเพื่อสนับสนุนตัวชี้เช่นเดียวกับการอ้างอิง
David Stone

34

ฉันคิดว่าโซลูชันของ Scott Meyers สามารถปรับปรุงได้ใน C ++ 11 โดยใช้ฟังก์ชันตัวช่วยชั่วคราว สิ่งนี้ทำให้ความตั้งใจชัดเจนมากขึ้นและสามารถนำมาใช้ซ้ำสำหรับผู้ได้รับรายอื่น ๆ

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

ฟังก์ชันตัวช่วยนี้สามารถใช้วิธีต่อไปนี้

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

อาร์กิวเมนต์แรกเป็นตัวชี้นี้เสมอ ประการที่สองคือตัวชี้ไปยังฟังก์ชั่นสมาชิกที่จะโทร หลังจากนั้นจำนวนอาร์กิวเมนต์เพิ่มเติมสามารถส่งผ่านได้เพื่อให้สามารถส่งต่อไปยังฟังก์ชันได้ สิ่งนี้ต้องการ C ++ 11 เนื่องจากเทมเพลต Variadic


3
มันเป็นความอัปยศเราไม่ได้ไปด้วยstd::remove_bottom_const std::remove_const
TBBle

ฉันไม่ชอบโซลูชันนี้เพราะมันยังคงฝังconst_castอยู่ คุณสามารถสร้างgetElementเทมเพลตเองและใช้ลักษณะของประเภทภายในกับmpl::conditionalประเภทที่คุณต้องการเช่นiterators หรือconstiterators หากจำเป็น ปัญหาที่แท้จริงคือวิธีการสร้างรุ่น const ของวิธีการเมื่อส่วนนี้ของลายเซ็นไม่สามารถ templatized?
v.oddou

2
@ v.oddou: std::remove_const<int const&>คือint const &(ลบการconstรับรองระดับบนสุด) ดังนั้นยิมนาสติกของNonConst<T>คำตอบนี้ สมมุติstd::remove_bottom_constสามารถลบด้านล่างระดับconstวุฒิการศึกษาและทำสิ่งที่NonConst<T>ไม่ที่นี่: =>std::remove_bottom_const<int const&>::type int&
TBBle

4
โซลูชันนี้ใช้งานไม่ได้หากgetElementโหลดมากเกินไป จากนั้นตัวชี้ฟังก์ชั่นไม่สามารถแก้ไขได้โดยไม่ต้องให้พารามิเตอร์เทมเพลตอย่างชัดเจน ทำไม?
John

1
คุณต้องแก้ไขคำตอบให้ใช้การส่งต่อที่สมบูรณ์แบบของ C ++ 11: likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }เสร็จสมบูรณ์: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83
ShaulF

22

verbose มากกว่า Meyers เล็กน้อย แต่ฉันอาจทำสิ่งนี้:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

เมธอดไพรเวตมีคุณสมบัติที่ไม่พึงประสงค์ซึ่งจะส่งคืนค่า Z ที่ไม่ใช่ const และสำหรับอินสแตนซ์ const ซึ่งเป็นสาเหตุที่ทำให้เป็นส่วนตัว วิธีการส่วนตัวอาจทำลายค่าคงที่ของอินเทอร์เฟซภายนอก (ในกรณีนี้ค่าคงที่ที่ต้องการคือ "วัตถุ const ไม่สามารถแก้ไขได้ผ่านการอ้างอิงที่ได้รับผ่านไปยังวัตถุที่มี -a")

โปรดทราบว่าความคิดเห็นเป็นส่วนหนึ่งของรูปแบบ - ส่วนติดต่อของ _getZ ระบุว่ามันไม่ถูกต้องที่จะเรียกมัน (นอกเหนือจาก accessors อย่างชัดเจน): ไม่มีประโยชน์เท่าที่จะเป็นไปได้เพราะมันเป็น 1 ตัวอักษรที่จะพิมพ์และจะไม่ ส่งผลให้รหัสมีขนาดเล็กลงหรือเร็วขึ้น การเรียกเมธอดนั้นเทียบเท่ากับการเรียกหนึ่งใน accessors ด้วย const_cast และคุณไม่ต้องการทำเช่นนั้น หากคุณกังวลเกี่ยวกับการทำข้อผิดพลาดอย่างชัดเจน (และนั่นเป็นเป้าหมายที่ยุติธรรม) ให้เรียกว่า const_cast_getZ แทน _getZ

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

[แก้ไข: เควินชี้ให้เห็นอย่างถูกต้องว่า _getZ อาจต้องการเรียกใช้วิธีการเพิ่มเติม (พูดว่า generateZ) ซึ่งเป็นผู้เชี่ยวชาญเฉพาะในลักษณะเดียวกับ getZ ในกรณีนี้ _getZ จะเห็น const Z & และต้อง const_cast ก่อนกลับ มันยังคงปลอดภัยเนื่องจากทุกอย่างของอุปกรณ์ควบคุมความร้อน แต่มันไม่ชัดเจนว่าปลอดภัย นอกจากนี้ถ้าคุณทำอย่างนั้นแล้วเปลี่ยน generateZ ให้คืนค่า const เสมอคุณก็ต้องเปลี่ยน getZ ให้คืนค่า const เสมอ แต่คอมไพเลอร์จะไม่บอกคุณว่าคุณทำ

จุดหลังนั้นเกี่ยวกับคอมไพเลอร์ก็เป็นจริงตามรูปแบบที่แนะนำของ Meyers แต่จุดแรกเกี่ยวกับ const_cast ที่ไม่ชัดเจนนั้นไม่ใช่ ด้วยความสมดุลฉันคิดว่าถ้า _getZ กลายเป็นต้องการค่า const_cast สำหรับค่าส่งคืนรูปแบบนี้จะสูญเสียมูลค่าของมันมากกว่า Meyers เนื่องจากมันยังมีความเสียเปรียบเมื่อเทียบกับของเมเยอร์ฉันคิดว่าฉันจะเปลี่ยนไปใช้สถานการณ์ของเขา การปรับโครงสร้างจากที่หนึ่งไปอีกที่หนึ่งนั้นง่ายดาย - มันไม่ส่งผลกระทบต่อรหัสที่ถูกต้องอื่น ๆ ในชั้นเรียนเนื่องจากมีเพียงรหัสที่ไม่ถูกต้องเท่านั้น


3
สิ่งนี้ยังคงมีปัญหาที่สิ่งที่คุณส่งคืนอาจเป็นค่าคงที่สำหรับอินสแตนซ์คงที่ของ X ในกรณีนี้คุณยังต้องใช้ const_cast ใน _getZ (... ) หากนักพัฒนาในทางที่ผิดในภายหลังก็ยังสามารถนำไปสู่ ​​UB หากสิ่งที่จะถูกส่งคืนคือ 'ไม่แน่นอน' นี่เป็นทางออกที่ดี
Kevin

1
ฟังก์ชั่นส่วนตัวใด ๆ (Heck, ฟังก์ชั่นสาธารณะ) สามารถใช้งานผิดพลาดโดยผู้พัฒนาในภายหลังหากพวกเขาเลือกที่จะเพิกเฉยต่อคำแนะนำ BLOCK CAPITAL เกี่ยวกับการใช้งานที่ถูกต้องในไฟล์ส่วนหัวและใน Doxygen เป็นต้นฉันไม่สามารถหยุด และฉันไม่คิดว่าเป็นปัญหาเพราะคำแนะนำนั้นง่ายต่อการเข้าใจ
Steve Jessop

13
-1: วิธีนี้ใช้ไม่ได้ในหลายสถานการณ์ เกิดอะไรขึ้นถ้าsomethingใน_getZ()ฟังก์ชันเป็นตัวแปรอินสแตนซ์ คอมไพเลอร์ (หรืออย่างน้อยบางคอมไพเลอร์) จะบ่นว่าตั้งแต่_getZ()เป็น const ตัวแปรอินสแตนซ์ใด ๆ ที่อ้างอิงภายในคือ const ด้วย ดังนั้นsomethingจะเป็น const (เป็นประเภทconst Z&) และไม่สามารถแปลงเป็นZ&ได้ จากประสบการณ์ของฉัน (ค่อนข้าง จำกัด ) ยอมรับว่าส่วนใหญ่somethingเป็นตัวแปรอินสแตนซ์ในกรณีเช่นนี้
แรงดึงดูด

2
@GravityBringer แล้ว "สิ่งที่" const_castจำเป็นต้องเกี่ยวข้องกับ มันตั้งใจที่จะเป็นตัวยึดตำแหน่งสำหรับรหัสที่จำเป็นในการรับผลตอบแทนที่ไม่ใช่ const จากวัตถุ const ไม่ใช่ตัวยึดตำแหน่งสำหรับสิ่งที่จะได้รับในการทะเยอทะยานซ้ำ ดังนั้น "บางอย่าง" ไม่ได้เป็นเพียงตัวแปรอินสแตนซ์
Steve Jessop

2
ฉันเห็น. ที่ลดประโยชน์ของเทคนิคจริงๆ ฉันจะลบ downvote แต่ดังนั้นฉันจะไม่ยอมให้
แรงดึงดูด

22

คำถามที่ดีและคำตอบที่ดี ฉันมีวิธีแก้ไขปัญหาอื่นที่ไม่มีการปลดเปลื้อง:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

อย่างไรก็ตามมีความน่าเกลียดที่ต้องมีสมาชิกแบบสแตติกและความต้องการในการใช้ instanceตัวแปรภายใน

ฉันไม่ได้พิจารณาถึงผลกระทบ (เชิงลบ) ที่เป็นไปได้ทั้งหมดของการแก้ปัญหานี้ โปรดแจ้งให้เราทราบหากมี


4
เอาล่ะลองดูข้อเท็จจริงที่ว่าคุณได้เพิ่มสำเร็จรูปมากขึ้น auto get(std::size_t i) -> auto(const), auto(&&)ถ้ามีอะไรที่นี้ควรจะใช้เป็นตัวอย่างว่าทำไมภาษาที่ต้องการวิธีการที่จะปรับเปลี่ยนฟังก์ชั่นบ่นพร้อมกับชนิดกลับไป ทำไม '&&' Ahh ดังนั้นฉันสามารถพูดได้:auto foo() -> auto(const), auto(&&) = delete;
kfsone

gd1: สิ่งที่ฉันมีอยู่ในใจ @ kfsone และสิ่งที่ฉันได้ข้อสรุปเช่นกัน
v.oddou

1
@kfsone ไวยากรณ์ควรจะรวมthisคำหลัก ผมขอแนะนำให้template< typename T > auto myfunction(T this, t args) -> decltype(ident)คำนี้จะได้รับการยอมรับเป็นอาร์กิวเมนต์ตัวอย่างวัตถุโดยปริยายและปล่อยให้คอมไพเลอร์รับรู้ MyFunction Tที่เป็นสมาชิกหรือ Tจะถูกย่อโดยอัตโนมัติบนไซต์การโทรซึ่งจะเป็นประเภทของคลาสเสมอ แต่จะมีการรับรอง cv ฟรี
v.oddou

2
การแก้ปัญหาที่ยังมีข้อได้เปรียบ (เมื่อเทียบกับconst_castหนึ่ง) เพื่อให้ที่จะกลับมาและiterator const_iterator
Jarod42

1
หากมีการย้ายการใช้งานในไฟล์ cpp (และเนื่องจากวิธีการที่จะไม่ทำซ้ำไม่ควรเป็นเรื่องเล็กน้อยมันอาจจะเป็นกรณี) staticสามารถทำได้ที่ขอบเขตไฟล์แทนขอบเขตของคลาส :-)
Jarod42

8

คุณสามารถแก้ไขได้ด้วยเทมเพลต วิธีการแก้ปัญหานี้น่าเกลียดเล็กน้อย (แต่ความอัปลักษณ์ถูกซ่อนอยู่ในไฟล์. cpp) แต่ให้การตรวจสอบคอมไพเลอร์ของ constness และไม่มีการทำซ้ำรหัส

ไฟล์. h:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

ไฟล์. cpp:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

ข้อเสียเปรียบหลักที่ฉันเห็นคือเนื่องจากการใช้วิธีการที่ซับซ้อนทั้งหมดอยู่ในฟังก์ชั่นระดับโลกคุณต้องได้รับสมาชิกของ X โดยใช้วิธีสาธารณะเช่น GetVector () ด้านบน (ซึ่งต้องมีเสมอ รุ่น const และไม่ใช่ const) หรือคุณอาจทำให้ฟังก์ชั่นนี้เป็นเพื่อนได้ แต่ฉันไม่ชอบเพื่อน

[แก้ไข: ลบรวม cstdio ที่ไม่จำเป็นออกระหว่างการทดสอบ]


3
คุณสามารถทำให้ฟังก์ชันการใช้งานที่ซับซ้อนเป็นสมาชิกแบบสแตติกเพื่อเข้าถึงสมาชิกส่วนตัว จำเป็นต้องประกาศฟังก์ชันในไฟล์ส่วนหัวของคลาสคำจำกัดความสามารถอยู่ในไฟล์การนำไปใช้งานของคลาสได้ มันเป็นส่วนหนึ่งของการนำคลาสไปใช้ทั้งหมด
CB Bailey

ใช่ใช่ความคิดที่ดี! ฉันไม่ชอบสิ่งที่เทมเพลตปรากฏในส่วนหัว แต่ถ้าเนื่องจากที่นี่อาจทำให้การดำเนินการค่อนข้างง่ายขึ้นมากมันอาจคุ้มค่า
Andy Balaam

+1 วิธีนี้ซึ่งไม่ได้ทำซ้ำรหัสใด ๆ หรือใช้น่าเกลียดใด ๆconst_cast(ซึ่งอาจถูกใช้โดยบังเอิญเพื่อ canst บางสิ่งบางอย่างที่จริงควรจะ const กับสิ่งที่ไม่ได้)
HelloGoodbye

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

3

วิธีการเกี่ยวกับการย้ายตรรกะเป็นวิธีส่วนตัวและเพียงทำ "รับการอ้างอิงและส่งคืน" สิ่งภายใน getters? ที่จริงแล้วฉันค่อนข้างสับสนเกี่ยวกับสแตติกและตัวละครในฟังก์ชัน getter ที่เรียบง่ายและฉันคิดว่าน่าเกลียดยกเว้นสถานการณ์ที่หายากมาก!


เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดคุณยังต้องใช้ const_cast ดูคำตอบของ Martin York และความคิดเห็นของฉันที่นั่น
Kevin

1
Kevin คำตอบของมาร์ตินยอร์ค
ปีเตอร์นิมโม

2

มันโกงที่จะใช้ตัวประมวลผลล่วงหน้าหรือไม่?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

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


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

2

เป็นเรื่องที่น่าแปลกใจสำหรับฉันที่มีคำตอบที่แตกต่างกันมากมาย แต่เกือบทั้งหมดล้วนพึ่งพาเวทมนตร์แม่แบบ เทมเพลตนั้นทรงพลัง แต่บางครั้งแมโครก็ใช้งานได้อย่างกระชับ ความเก่งกาจสูงสุดมักจะทำได้โดยการรวมทั้งสองอย่างเข้าด้วยกัน

ฉันเขียนมาโคร FROM_CONST_OVERLOAD()ซึ่งสามารถวางในฟังก์ชันที่ไม่ใช่ const เพื่อเรียกใช้ฟังก์ชัน const

ตัวอย่างการใช้งาน:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

ใช้งานง่ายและนำมาใช้ใหม่:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

คำอธิบาย:

ตามที่โพสต์ในคำตอบมากมายรูปแบบทั่วไปเพื่อหลีกเลี่ยงการทำซ้ำรหัสในฟังก์ชั่นสมาชิกที่ไม่ใช่สมาชิกคือ:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

หม้อต้มน้ำจำนวนมากสามารถหลีกเลี่ยงได้โดยใช้การอนุมานประเภท ก่อนอื่นconst_castสามารถห่อหุ้มWithoutConst()ซึ่ง infers ประเภทของการโต้แย้งและลบ const-qualifier ประการที่สองวิธีการที่คล้ายกันสามารถใช้ในWithConst()การ const- คัดเลือกthisตัวชี้ซึ่งช่วยให้การเรียกวิธีการ const-overloaded

ส่วนที่เหลือเป็นแมโครอย่างง่ายที่นำหน้าการโทรด้วยคุณสมบัติที่ถูกต้องthis->และลบ const จากผลลัพธ์ เนื่องจากนิพจน์ที่ใช้ในแมโครนั้นมักจะเป็นการเรียกใช้ฟังก์ชั่นแบบง่าย ๆ โดยมีอาร์กิวเมนต์ที่ส่งต่อ 1: 1 ข้อเสียของมาโครเช่นการประเมินหลาย ๆ ครั้งจึงไม่เริ่มขึ้น__VA_ARGS__ยังสามารถใช้งานได้ ตัวแยกอาร์กิวเมนต์) เกิดขึ้นภายในวงเล็บ

วิธีนี้มีประโยชน์หลายประการ:

  • ไวยากรณ์น้อยที่สุดและเป็นธรรมชาติ - เพียงแค่วางสาย FROM_CONST_OVERLOAD( )
  • ไม่จำเป็นต้องใช้ฟังก์ชั่นพิเศษของสมาชิก
  • เข้ากันได้กับ C ++ 98
  • ใช้งานง่ายไม่มี metaprogramming แม่แบบและการอ้างอิงเป็นศูนย์
  • Extensible: ความสัมพันธ์ระหว่าง const อื่น ๆ สามารถเพิ่ม (เช่นconst_iterator, std::shared_ptr<const T>ฯลฯ ) สำหรับเรื่องนี้เพียงแค่โอเวอร์โหลดWithoutConst()สำหรับประเภทที่เกี่ยวข้อง

ข้อ จำกัด : โซลูชันนี้ได้รับการปรับให้เหมาะสมที่สุดสำหรับสถานการณ์ที่การโอเวอร์โหลดที่ไม่ใช่ const ดำเนินการตรงกับ const โอเวอร์โหลดดังนั้นอาร์กิวเมนต์สามารถส่งต่อได้ 1: 1 หากตรรกะของคุณแตกต่างและคุณไม่ได้เรียกเวอร์ชัน const ผ่านthis->Method(args)คุณอาจพิจารณาแนวทางอื่น ๆ


2

สำหรับผู้ที่ (เช่นฉัน) ที่

  • ใช้c ++ 17
  • ต้องการเพิ่มจำนวนแผ่นอย่างน้อย / การทำซ้ำและ
  • ไม่ต้องกังวลกับการใช้มาโคร (ในขณะที่รอเมตาคลาส ... )

นี่คืออีกหนึ่ง:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T> auto func(T&&... a)                            \
        -> typename NonConst<decltype(func(std::forward<T>(a)...))>::type   \
    {                                                                       \
        return const_cast<decltype(func(std::forward<T>(a)...))>(           \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

มันเป็นการผสมผสานระหว่างคำตอบจาก @Pait, @DavidStone และ @ sh1 ( แก้ไข : และปรับปรุงจาก @cdhowie) สิ่งที่เพิ่มลงในตารางคือคุณจะได้รับโค้ดพิเศษอีกหนึ่งบรรทัดซึ่งจะตั้งชื่อฟังก์ชั่น (แต่ไม่มีอาร์กิวเมนต์หรือการทำซ้ำชนิดส่งคืน):

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

หมายเหตุ: gcc ล้มเหลวในการรวบรวมสิ่งนี้ก่อนหน้า 8.1, เสียงดังกราว -5 ขึ้นไปและ MSVC-19 มีความสุข (อ้างอิงจากคอมไพเลอร์ explorer )


นี่ใช้การได้โดยตรงสำหรับฉัน นี่เป็นคำตอบที่ดีมากขอบคุณ!
สั้น

ไม่ควรdecltype()ใช้std::forwardกับอาร์กิวเมนต์เพื่อให้แน่ใจว่าเราใช้ชนิดส่งคืนที่ถูกต้องในกรณีที่เรามีการอ้างอิงมากเกินไปget()ซึ่งใช้การอ้างอิงประเภทต่างๆ
cdhowie

@cdhowie คุณช่วยยกตัวอย่างได้ไหม?
axxel

@axxel มัน contrived เป็นนรก แต่นี่คุณไป NON_CONSTฉงนฉงายแมโครประเภทกลับไม่ถูกต้องและconst_castเพื่อผิดประเภทเนื่องจากการขาดการส่งต่อในส่วนdecltype(func(a...))ประเภท แทนที่พวกเขาด้วยแก้นี้decltype(func(std::forward<T>(a)...)) (มีเพียงข้อผิดพลาดลิงเกอร์เป็นเพราะฉันไม่เคยกำหนดใด ๆ ของประกาศX::getoverloads.)
cdhowie

1
ขอบคุณ @cdhowie ฉันเก็บตัวอย่างของคุณเพื่อใช้งานเกินพิกัดที่ไม่ใช่จริง: coliru.stacked-crooked.com/a/0cedc7f4e789479e
axxel

1

นี่คือฟังก์ชันตัวช่วยคงที่แม่แบบรุ่น C ++ 17 พร้อมด้วยและการทดสอบเสริม SFINAE

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

เวอร์ชันเต็ม: https://godbolt.org/z/mMK4r3


1

ฉันสร้างแมโครขึ้นมาเพื่อสร้างฟังก์ชั่น const / ไม่ใช่ const โดยอัตโนมัติ

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

ดูจุดสิ้นสุดของคำตอบสำหรับการนำไปใช้

อาร์กิวเมนต์ของMAYBE_CONSTซ้ำซ้อน ในสำเนาแรกCVจะถูกแทนที่ด้วยไม่มีอะไร constและสำเนาที่สองก็ถูกแทนที่ด้วย

ไม่มีการ จำกัด จำนวนครั้งที่CVสามารถปรากฏในอาร์กิวเมนต์แมโคร

แม้ว่าจะมีความไม่สะดวกเล็กน้อย หากCVปรากฏอยู่ในวงเล็บวงเล็บจะต้องนำหน้าด้วยCV_IN:

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

การดำเนินงาน:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

การใช้งานล่วงหน้า C ++ 20 ที่ไม่รองรับCV_IN:

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end

0

โดยทั่วไปฟังก์ชั่นสมาชิกที่คุณต้องการรุ่น const และไม่ใช่ const คือ getters และ setters ส่วนใหญ่เวลาพวกเขาเป็นหนึ่ง liners ดังนั้นการทำสำเนารหัสไม่ใช่ปัญหา


2
นั่นอาจเป็นจริงเกือบตลอดเวลา แต่มีข้อยกเว้นอยู่
เควิน

1
getters แล้วตัวตั้งค่า const ไม่ได้ทำให้ความรู้สึกมาก;)
jwfearn

ฉันหมายความว่าผู้ทะทะทะลวงที่ไม่ใช่ const นั้นเป็นตัวตั้งตัวได้อย่างมีประสิทธิภาพ :)
Dima

0

ฉันทำสิ่งนี้กับเพื่อนที่ชอบธรรมอย่างชอบธรรมในการใช้const_cast... โดยไม่รู้ว่าฉันอาจทำอะไรแบบนี้ (ไม่หรูหราจริงๆ):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}

0

ฉันขอแนะนำแม่แบบฟังก์ชั่นคงผู้ช่วยส่วนตัวเช่นนี้

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};

-1

บทความ DDJ นี้แสดงวิธีการใช้เทมเพลตเฉพาะทางที่ไม่ต้องการให้คุณใช้ const_cast สำหรับฟังก์ชั่นที่เรียบง่ายมันไม่จำเป็นจริงๆ

เพิ่ม :: any_cast (ณ จุดหนึ่งมันไม่ได้อีกแล้ว) ใช้ const_cast จากรุ่น const เรียกรุ่นที่ไม่ใช่ const เพื่อหลีกเลี่ยงการทำซ้ำ คุณไม่สามารถกำหนดความหมายของ const ในเวอร์ชันที่ไม่ใช่ const ได้ดังนั้นคุณต้องเป็นอย่างมากระมัดระวังกับว่า

ในที่สุดการทำสำเนารหัสก็ไม่เป็นไรตราบใดที่ข้อมูลโค้ดทั้งสองนั้นวางซ้อนกันโดยตรง


บทความ DDJ ดูเหมือนจะอ้างถึงตัววนซ้ำ - ซึ่งไม่เกี่ยวข้องกับคำถาม Const-iterator ไม่ใช่ข้อมูลคงที่ - มันเป็นตัววนซ้ำที่ชี้ไปยังข้อมูลคงที่
Kevin

-1

หากต้องการเพิ่มโซลูชัน jwfearn และ kevin ที่ให้มานี่คือโซลูชันที่สอดคล้องกันเมื่อฟังก์ชันส่งคืน shared_ptr:

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};

-1

ไม่พบสิ่งที่ฉันกำลังมองหาดังนั้นฉันจึงรีดเป็นของตัวเอง ...

อันนี้เป็นคำเล็กน้อย แต่มีประโยชน์ในการจัดการวิธีการโอเวอร์โหลดจำนวนมากในชื่อเดียวกัน (และประเภทผลตอบแทน) ทั้งหมดในครั้งเดียว:

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

หากคุณมีเพียงหนึ่งconstวิธีต่อชื่อ แต่ยังมีวิธีการทำซ้ำมากมายคุณอาจต้องการสิ่งนี้:

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

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

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

แต่ข้อโต้แย้งการอ้างอิงถึงconstวิธีการล้มเหลวในการจับคู่กับข้อโต้แย้งโดยค่าที่เห็นได้ชัดกับแม่แบบและมันจะแบ่ง ไม่แน่ใจว่าทำไม นี่คือเหตุผลที่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.