initializer_list และย้ายความหมาย


99

ฉันได้รับอนุญาตให้ย้ายองค์ประกอบออกจากไฟล์ std::initializer_list<T> ?

#include <initializer_list>
#include <utility>

template<typename T>
void foo(std::initializer_list<T> list)
{
    for (auto it = list.begin(); it != list.end(); ++it)
    {
        bar(std::move(*it));   // kosher?
    }
}

เนื่องจากstd::intializer_list<T>ต้องการความสนใจของคอมไพเลอร์เป็นพิเศษและไม่มีค่าความหมายเหมือนคอนเทนเนอร์ปกติของไลบรารีมาตรฐาน C ++ ฉันควรจะปลอดภัยดีกว่าขอโทษและถาม


ข้อกำหนดเกี่ยวภาษาหลักที่วัตถุที่อ้างถึงโดยinitializer_list<T>มีไม่ใช่ -const ชอบinitializer_list<int>หมายถึงintวัตถุ แต่ฉันคิดว่านั่นเป็นข้อบกพร่อง - มีจุดมุ่งหมายเพื่อให้คอมไพเลอร์สามารถจัดสรรรายการในหน่วยความจำแบบอ่านอย่างเดียวแบบคงที่
Johannes Schaub - litb

คำตอบ:


93

ไม่ได้ผลตามที่ตั้งใจไว้ คุณจะยังคงได้รับสำเนา ฉันค่อนข้างประหลาดใจกับสิ่งนี้เพราะฉันคิดว่าคงinitializer_listมีอยู่เพื่อคงรูปแบบของจังหวะไว้จนกว่าจะถึงเวลาmove'd

beginและendสำหรับการinitializer_listส่งคืนconst T *ดังนั้นผลลัพธ์ของmoveโค้ดของคุณคือT const &&- การอ้างอิง rvalue ที่ไม่เปลี่ยนรูป สำนวนดังกล่าวไม่สามารถเคลื่อนย้ายได้อย่างมีความหมาย มันจะผูกกับพารามิเตอร์ของฟังก์ชันประเภทT const &เนื่องจาก rvalues ​​ผูกกับการอ้างอิง const lvalue และคุณจะยังคงเห็นความหมายของการคัดลอก

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

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


11
ในกรณีที่ไม่ชัดเจนก็ยังคงหมายความว่าการใช้งานstd::moveนั้นปลอดภัยหากไม่ได้ผล (จำกัด การT const&&ย้ายผู้ก่อสร้าง)
Luc Danton

ฉันไม่คิดว่าคุณจะโต้แย้งทั้งหมดได้const std::initializer_list<T>หรือแค่std::initializer_list<T>ในแบบที่ไม่ทำให้เกิดความประหลาดใจบ่อยนัก พิจารณาว่าแต่ละอาร์กิวเมนต์ในinitializer_listสามารถเป็นได้constหรือไม่และเป็นที่รู้กันในบริบทของผู้เรียก แต่คอมไพเลอร์ต้องสร้างโค้ดเพียงเวอร์ชันเดียวในบริบทของคาลลี (กล่าวคือภายในfooจะไม่รู้อะไรเลยเกี่ยวกับอาร์กิวเมนต์ ที่ผู้โทรเข้ามา)
David Rodríguez - dribeas

1
@ เดวิด: จุดดี แต่ก็ยังมีประโยชน์ที่จะมีการstd::initializer_list &&โอเวอร์โหลดทำบางสิ่งแม้ว่าจะต้องมีการโอเวอร์โหลดแบบไม่อ้างอิงก็ตาม ฉันคิดว่ามันจะสับสนยิ่งกว่าสถานการณ์ปัจจุบันซึ่งมันแย่อยู่แล้ว
Potatoswatter

1
@JBJansen ไม่สามารถแฮ็คได้ ฉันไม่เห็นว่าโค้ดนั้นควรจะใช้งาน wrt initializer_list ได้อย่างไร แต่เนื่องจากผู้ใช้คุณไม่มีสิทธิ์ที่จำเป็นในการย้ายจากมัน รหัสปลอดภัยจะไม่ทำเช่นนั้น
Potatoswatter

2
@ Potatoswatter ความคิดเห็นที่ล่าช้า แต่สถานะของข้อเสนอคืออะไร มีโอกาสระยะไกลที่อาจทำให้เป็น C ++ 20 ได้หรือไม่?
WhiZTiM

20
bar(std::move(*it));   // kosher?

ไม่เป็นไปในทางที่คุณตั้งใจ คุณไม่สามารถย้ายconstวัตถุได้ และstd::initializer_listให้constการเข้าถึงเฉพาะองค์ประกอบเท่านั้น ดังนั้นประเภทของitคือconst T *มี

การพยายามโทรของคุณstd::move(*it)จะส่งผลให้เป็นค่า l เท่านั้น IE: สำเนา

std::initializer_listอ้างอิงหน่วยความจำแบบคงที่ นั่นคือสิ่งที่ชั้นเรียนมีไว้สำหรับ คุณไม่สามารถย้ายจากหน่วยความจำคงที่ได้เนื่องจากการเคลื่อนไหวหมายถึงการเปลี่ยนแปลง คุณสามารถคัดลอกได้เท่านั้น


const xvalue ยังคงเป็น xvalue และinitializer_listอ้างอิงสแต็กหากจำเป็น (หากเนื้อหาไม่คงที่แสดงว่ายังปลอดภัยต่อเธรด)
Potatoswatter

5
@ Potatoswatter: คุณไม่สามารถย้ายจากวัตถุคงที่ initializer_listวัตถุตัวเองอาจจะเป็น Xvalue แต่เนื้อหาของมัน (อาร์เรย์ที่เกิดขึ้นจริงของค่าที่จะชี้ไป) เป็นconstเพราะเนื้อหาเหล่านั้นอาจจะเป็นค่าคงที่ คุณไม่สามารถย้ายจากเนื้อหาของไฟล์initializer_list.
Nicol Bolas

ดูคำตอบและการอภิปรายของฉัน เขาได้ย้ายตัววนซ้ำ dereferenced สร้างconstxvalue moveอาจไม่มีความหมาย แต่ถูกกฎหมายและเป็นไปได้ที่จะประกาศพารามิเตอร์ที่ยอมรับเพียงแค่นั้น หากการย้ายประเภทใดประเภทหนึ่งเกิดขึ้นเป็นการไม่ดำเนินการอาจทำงานได้อย่างถูกต้อง
Potatoswatter

1
@Potatoswatter: c ++ 11 expends std::moveมาตรฐานจำนวนมากของภาษาเพื่อให้มั่นใจว่าไม่ใช่วัตถุชั่วคราวจะไม่ได้ย้ายจริงเว้นแต่คุณจะใช้ สิ่งนี้ช่วยให้มั่นใจได้ว่าคุณสามารถบอกได้จากการตรวจสอบเมื่อการดำเนินการย้ายเกิดขึ้นเนื่องจากจะส่งผลกระทบต่อทั้งต้นทางและปลายทาง (คุณไม่ต้องการให้เกิดขึ้นโดยปริยายสำหรับอ็อบเจ็กต์ที่ระบุชื่อ) ด้วยเหตุนี้หากคุณใช้std::moveในสถานที่ที่การดำเนินการย้ายไม่เกิดขึ้น (และจะไม่มีการเคลื่อนไหวเกิดขึ้นจริงหากคุณมีค่าconstxvalue) รหัสจึงทำให้เข้าใจผิด ฉันคิดว่ามันเป็นความผิดพลาดstd::moveที่สามารถเรียกconstวัตถุได้
Nicol Bolas

1
อาจจะ แต่ฉันจะยังคงใช้ข้อยกเว้นน้อยลงสำหรับกฎเกี่ยวกับความเป็นไปได้ของรหัสที่ทำให้เข้าใจผิด อย่างไรก็ตามนั่นคือเหตุผลที่ฉันตอบว่า "ไม่" แม้ว่ามันจะถูกกฎหมายก็ตามและผลลัพธ์ก็คือ xvalue แม้ว่ามันจะผูกเป็นค่า const lvalue ก็ตาม พูดตามตรงฉันเคยเกี้ยวพาราสีสั้น ๆconst &&ในชั้นเรียนเก็บขยะพร้อมพอยน์เตอร์ที่มีการจัดการซึ่งทุกอย่างที่เกี่ยวข้องไม่แน่นอนและการเคลื่อนย้ายย้ายการจัดการตัวชี้ แต่ไม่ส่งผลต่อค่าที่มี มีกรณีขอบที่ยุ่งยากเสมอ: v)
Potatoswatter

3

สิ่งนี้จะไม่ทำงานตามที่ระบุไว้เนื่องจากlist.begin()มีประเภทconst T *และไม่มีทางที่คุณจะย้ายจากวัตถุคงที่ได้ นักออกแบบภาษาอาจทำเช่นนั้นเพื่อให้รายการเริ่มต้นมีค่าคงที่ของสตริงซึ่งจะไม่เหมาะสมที่จะย้าย

อย่างไรก็ตามหากคุณอยู่ในสถานการณ์ที่คุณรู้ว่ารายการ initializer มีนิพจน์ rvalue (หรือคุณต้องการบังคับให้ผู้ใช้เขียนสิ่งเหล่านั้น) มีเคล็ดลับที่จะทำให้มันใช้งานได้ (ฉันได้รับแรงบันดาลใจจากคำตอบของ Sumant สำหรับ แต่วิธีแก้ปัญหานั้นง่ายกว่าวิธีนั้น) คุณจำเป็นต้องมีองค์ประกอบที่เก็บไว้ในรายการ initialiser ที่จะไม่ได้Tค่า T&&แต่ค่าที่แค็ปซูล จากนั้นแม้ว่าค่าเหล่านั้นจะconstมีคุณสมบัติเหมาะสม แต่ก็ยังสามารถดึงค่า rvalue ที่ปรับเปลี่ยนได้

template<typename T>
  class rref_capture
{
  T* ptr;
public:
  rref_capture(T&& x) : ptr(&x) {}
  operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

ตอนนี้แทนที่จะประกาศinitializer_list<T>อาร์กิวเมนต์คุณประกาศinitializer_list<rref_capture<T> >อาร์กิวเมนต์ นี่คือตัวอย่างที่เป็นรูปธรรมซึ่งเกี่ยวข้องกับเวกเตอร์ของพstd::unique_ptr<int>อยน์เตอร์อัจฉริยะซึ่งกำหนดเฉพาะความหมายการย้าย (ดังนั้นอ็อบเจ็กต์เหล่านี้จะไม่สามารถเก็บไว้ในรายการตัวเริ่มต้น) แต่รายการ initializer ด้านล่างรวบรวมได้โดยไม่มีปัญหา

#include <memory>
#include <initializer_list>
class uptr_vec
{
  typedef std::unique_ptr<int> uptr; // move only type
  std::vector<uptr> data;
public:
  uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
  uptr_vec(std::initializer_list<rref_capture<uptr> > l)
    : data(l.begin(),l.end())
  {}
  uptr_vec& operator=(const uptr_vec&) = delete;
  int operator[] (size_t index) const { return *data[index]; }
};

int main()
{
  std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
  uptr_vec v { std::move(a), std::move(b), std::move(c) };
  std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}

คำถามหนึ่งข้อต้องการคำตอบ: หากองค์ประกอบของรายการตัวเริ่มต้นควรเป็น prvalues ​​จริง (ในตัวอย่างคือ xvalues) ภาษาจะทำให้แน่ใจได้หรือไม่ว่าอายุการใช้งานของโมเมนต์ที่เกี่ยวข้องจะขยายไปจนถึงจุดที่ใช้ ตรงไปตรงมาฉันไม่คิดว่ามาตรา 8.5 ที่เกี่ยวข้องของมาตรฐานจะช่วยแก้ปัญหานี้ได้เลย อย่างไรก็ตามเมื่ออ่าน 1.9: 10 ดูเหมือนว่านิพจน์ทั้งหมดที่เกี่ยวข้องในทุกกรณีจะครอบคลุมการใช้รายการตัวเริ่มต้นดังนั้นฉันคิดว่าไม่มีอันตรายจากการอ้างอิงค่า rvalue ที่ห้อยลงมา


ค่าคงที่สตริง? ชอบ"Hello world"? หากคุณย้ายจากสิ่งเหล่านี้คุณเพียงแค่คัดลอกตัวชี้ (หรือผูกข้อมูลอ้างอิง)
dyp

1
"หนึ่งคำถามที่ไม่ต้องการคำตอบ" initializers ภายในถูกผูกไว้กับการอ้างอิงในพารามิเตอร์ของฟังก์ชั่น{..} rref_captureสิ่งนี้ไม่ได้ยืดอายุการใช้งาน แต่ยังคงถูกทำลายเมื่อสิ้นสุดการแสดงออกที่สมบูรณ์ที่พวกเขาสร้างขึ้น
dyp

ต่อTCความคิดเห็น 's จากคำตอบอื่น: ถ้าคุณมีหลายทับถมของ constructor, ห่อstd::initializer_list<rref_capture<T>>ในบางลักษณะการเปลี่ยนแปลงของการเลือกของคุณ - พูดstd::decay_t- เพื่อป้องกันการหักที่ไม่พึงประสงค์
Unslander Monica

2

ฉันคิดว่าการเสนอจุดเริ่มต้นที่สมเหตุสมผลสำหรับวิธีแก้ปัญหานั้นอาจเป็นประโยชน์

ความคิดเห็นในบรรทัด

#include <memory>
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <iterator>

template<class Array> struct maker;

// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
  using result_type = std::vector<T, A>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const -> result_type
  {
    result_type result;
    result.reserve(sizeof...(Ts));
    using expand = int[];
    void(expand {
      0,
      (result.push_back(std::forward<Ts>(ts)),0)...
    });

    return result;
  }
};

// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
  using result_type = std::array<T, N>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const
  {
    return result_type { std::forward<Ts>(ts)... };
  }

};

//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
  auto m = maker<Array>();
  return m(std::forward<Ts>(ts)...);
}

// vectors and arrays of non-copyable types
using vt = std::vector<std::unique_ptr<int>>;
using at = std::array<std::unique_ptr<int>,2>;


int main(){
    // build an array, using make<> for consistency
    auto a = make<at>(std::make_unique<int>(10), std::make_unique<int>(20));

    // build a vector, using make<> because an initializer_list requires a copyable type  
    auto v = make<vt>(std::make_unique<int>(10), std::make_unique<int>(20));
}

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

1
@underscore_d คุณพูดถูกจริงๆ ฉันเห็นว่าการแบ่งปันความรู้ที่เกี่ยวข้องกับคำถามเป็นสิ่งที่ดีในตัวมันเอง ในกรณีนี้บางทีมันอาจช่วย OP และบางทีมันก็ไม่ได้ - เขาไม่ตอบสนอง อย่างไรก็ตามบ่อยครั้งที่ OP และอื่น ๆ ยินดีต้อนรับเนื้อหาเพิ่มเติมที่เกี่ยวข้องกับคำถาม
Richard Hodges

แน่นอนว่าอาจช่วยได้แน่นอนสำหรับผู้อ่านที่ต้องการบางสิ่งบางอย่างinitializer_listแต่ไม่ได้อยู่ภายใต้ข้อ จำกัด ทั้งหมดที่ทำให้เป็นประโยชน์ :)
underscore_d

@underscore_d ข้อ จำกัด ใดที่ฉันมองข้ามไป
Richard Hodges

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

0

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

#include <vector>
#include <utility>

// begin helper functions

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

// end helper functions

struct S {
  S(int) {}
  S(S&&) {}
};

void bar(S&& s) {}

template <typename T, typename... Args>
void foo(Args&&... args) {
  std::vector<T> args_vec = make_vector<T>(std::forward<Args>(args)...);
  for (auto& arg : args_vec) {
    bar(std::move(arg));
  }
}

int main() {
  foo<S>(S(1), S(2), S(3));
  return 0;
}

เทมเพลต Variadic สามารถจัดการการอ้างอิง r-value ได้อย่างเหมาะสมซึ่งแตกต่างจาก initializer_list

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


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

0

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

คลาส Wrapper ได้รับการออกแบบมาเพื่อใช้ในทางที่std::moveใช้เพียงแค่แทนที่std::moveด้วยmove_wrapperแต่ต้องใช้ C ++ 17 สำหรับข้อมูลจำเพาะรุ่นเก่าคุณสามารถใช้วิธีการสร้างเพิ่มเติมได้

คุณจะต้องเขียน builder method / constructor ที่ยอมรับคลาส wrapper ภายใน initializer_listและย้ายองค์ประกอบตามนั้น

initializer_listหากคุณต้องการองค์ประกอบบางอย่างที่จะคัดลอกแทนการถูกย้ายไปสร้างสำเนาก่อนที่จะผ่านไปยัง

รหัสควรเป็นเอกสารในตัว

#include <iostream>
#include <vector>
#include <initializer_list>

using namespace std;

template <typename T>
struct move_wrapper {
    T && t;

    move_wrapper(T && t) : t(move(t)) { // since it's just a wrapper for rvalues
    }

    explicit move_wrapper(T & t) : t(move(t)) { // acts as std::move
    }
};

struct Foo {
    int x;

    Foo(int x) : x(x) {
        cout << "Foo(" << x << ")\n";
    }

    Foo(Foo const & other) : x(other.x) {
        cout << "copy Foo(" << x << ")\n";
    }

    Foo(Foo && other) : x(other.x) {
        cout << "move Foo(" << x << ")\n";
    }
};

template <typename T>
struct Vec {
    vector<T> v;

    Vec(initializer_list<T> il) : v(il) {
    }

    Vec(initializer_list<move_wrapper<T>> il) {
        v.reserve(il.size());
        for (move_wrapper<T> const & w : il) {
            v.emplace_back(move(w.t));
        }
    }
};

int main() {
    Foo x{1}; // Foo(1)
    Foo y{2}; // Foo(2)

    Vec<Foo> v{Foo{3}, move_wrapper(x), Foo{y}}; // I want y to be copied
    // Foo(3)
    // copy Foo(2)
    // move Foo(3)
    // move Foo(1)
    // move Foo(2)
}

0

แทนที่จะใช้ a std::initializer_list<T>คุณสามารถประกาศอาร์กิวเมนต์ของคุณเป็นการอ้างอิงอาร์เรย์ rvalue:

template <typename T>
void bar(T &&value);

template <typename T, size_t N>
void foo(T (&&list)[N] ) {
   std::for_each(std::make_move_iterator(std::begin(list)),
                 std::make_move_iterator(std::end(list)),
                 &bar);
}

void baz() {
   foo({std::make_unique<int>(0), std::make_unique<int>(1)});
}

ดูตัวอย่างโดยใช้std::unique_ptr<int>: https://gcc.godbolt.org/z/2uNxv6


-1

พิจารณาin<T>สำนวนที่อธิบายในcpptruths แนวคิดคือการกำหนด lvalue / rvalue ณ รันไทม์แล้วเรียกการย้ายหรือการสร้างสำเนา in<T>จะตรวจจับ rvalue / lvalue แม้ว่าอินเทอร์เฟซมาตรฐานที่จัดเตรียมโดย initializer_list จะอ้างอิง const


4
เหตุใดคุณจึงต้องการกำหนดหมวดหมู่ค่าที่รันไทม์บนโลกเมื่อคอมไพเลอร์รู้แล้ว
fredoverflow

1
โปรดอ่านบล็อกและแสดงความคิดเห็นหากคุณไม่เห็นด้วยหรือมีทางเลือกที่ดีกว่า แม้ว่าคอมไพลเลอร์จะทราบหมวดหมู่ค่า แต่ initializer_list จะไม่เก็บรักษาเนื่องจากมีตัววนซ้ำ const เท่านั้น ดังนั้นคุณต้อง "จับ" หมวดหมู่ค่าเมื่อคุณสร้าง initializer_list และส่งผ่านเพื่อให้ฟังก์ชันสามารถใช้ประโยชน์ได้ตามที่ต้องการ
Sumant

5
คำตอบนี้จะไร้ประโยชน์โดยทั่วไปหากไม่ทำตามลิงก์และคำตอบ SO ควรมีประโยชน์โดยไม่ต้องติดตามลิงก์
Yakk - Adam Nevraumont

1
@Sumant [คัดลอกความคิดเห็นของฉันจากโพสต์ที่เหมือนกันในที่อื่น ๆ ] ความยุ่งเหยิงอันน่าสยดสยองนั้นให้ประโยชน์ที่วัดผลได้ต่อประสิทธิภาพหรือการใช้งานหน่วยความจำจริงหรือไม่และหากเป็นเช่นนั้นผลประโยชน์จำนวนมากเพียงพอที่จะชดเชยได้อย่างเพียงพอว่ามันดูแย่แค่ไหนและความจริงที่ว่านั้น ใช้เวลาประมาณหนึ่งชั่วโมงในการคิดว่ามันพยายามทำอะไร? ฉันไม่สงสัยเลย
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.