ทำไมไม่อนุมานพารามิเตอร์เทมเพลตจากตัวสร้าง


102

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

template<typename obj>
class Variable {
      obj data;
      public: Variable(obj d)
              {
                   data = d;
              }
};

int main()
{
    int num = 2;
    Variable var(num); //would be equivalent to Variable<int> var(num),
    return 0;          //but actually a compile error
}

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


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

2
โปรดทราบว่าสิ่งนี้สามารถแก้ไขได้อย่างง่ายดายผ่านtemplate<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
Mooing Duck

3
คุณสามารถจัดเรียงสิ่งที่คุณต้องการได้ var = Variable <Decktype (n)> (n);
QuentinUK

18
C ++ 17 ก็ยอม! ข้อเสนอนี้ได้รับการยอมรับ: open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
underscore_d

1
@underscore_d สุดยอด! เกี่ยวกับเวลา! มันรู้สึกเป็นธรรมชาติสำหรับฉันนั่นคือวิธีที่ควรได้ผลและเป็นที่มาของการระคายเคืองที่มันไม่ได้
59

คำตอบ:


46

ฉันคิดว่ามันไม่ถูกต้องเพราะตัวสร้างไม่ใช่จุดเดียวของการเข้าคลาสเสมอไป (ฉันกำลังพูดถึงตัวสร้างการคัดลอกและตัวดำเนินการ =) สมมติว่าคุณกำลังใช้ชั้นเรียนของคุณดังนี้:

MyClass m(string s);
MyClass *pm;
*pm = m;

ฉันไม่แน่ใจว่ามันจะชัดเจนขนาดนี้สำหรับตัวแยกวิเคราะห์ที่จะรู้ว่า MyClass เป็นเทมเพลตประเภทใด

ไม่แน่ใจว่าสิ่งที่ฉันพูดนั้นสมเหตุสมผลหรือไม่ แต่อย่าลังเลที่จะเพิ่มความคิดเห็นนั่นเป็นคำถามที่น่าสนใจ

C ++ 17

เป็นที่ยอมรับว่า C ++ 17 จะมีการหักประเภทจากอาร์กิวเมนต์ตัวสร้าง

ตัวอย่าง:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

กระดาษได้รับการยอมรับ


8
นี่เป็นจุดที่ยอดเยี่ยมที่ฉันไม่เคยพิจารณา ฉันไม่เห็นความจริงที่ว่าตัวชี้จะต้องเป็นประเภทที่เฉพาะเจาะจง (กล่าวคือจะต้องเป็น MyClass <string> * pm) หากเป็นเช่นนั้นสิ่งที่คุณต้องทำคือช่วยตัวเองจากการระบุประเภทในการสร้างอินสแตนซ์ ตัวละครพิเศษเพียงไม่กี่ตัว (และเฉพาะในกรณีที่วัตถุถูกสร้างขึ้นบนสแต็กไม่ใช่ฮีปตามด้านบน) ฉันสงสัยเสมอว่าการอนุมานของคลาสอาจเปิดกระป๋องวากยสัมพันธ์ของเวิร์มและฉันคิดว่านี่อาจเป็นเช่นนั้น
GRB

2
ฉันไม่ค่อยเห็นว่าการอนุญาตให้ใช้การอนุมานพารามิเตอร์เทมเพลตจากตัวสร้างจำเป็นต้องอนุญาตให้มีการประกาศที่ไม่ใช่เฉพาะทางโดยไม่ต้องเรียกตัวสร้างเช่นเดียวกับในบรรทัดที่สองของคุณ กล่าวคือMyClass *pmที่นี่จะไม่ถูกต้องด้วยเหตุผลเดียวกับที่template <typename T> void foo();ไม่สามารถเรียกฟังก์ชันที่ประกาศได้หากไม่มีความเชี่ยวชาญอย่างชัดเจน
Kyle Strand

3
@KyleStrand ใช่โดยพูดว่า 'อาร์กิวเมนต์แม่แบบคลาสไม่สามารถอนุมานได้จากตัวสร้างเนื่องจาก[ตัวอย่างที่ไม่ใช้ตัวสร้างใด ๆ ] ' คำตอบนี้ไม่เกี่ยวข้องโดยสิ้นเชิง ฉันไม่อยากจะเชื่อเลยว่ามันได้รับการยอมรับอย่างแท้จริงถึง +29 ใช้เวลา 6 ปีเพื่อให้ใครบางคนสังเกตเห็นปัญหาที่เห็นได้ชัดและนั่งโดยไม่มีการโหวตลดลงเลยสักครั้งเป็นเวลา 7 ปี ไม่มีใครคิดในขณะที่พวกเขาอ่านหรือ ... ?
underscore_d

1
@underscore_d ฉันชอบวิธีที่เป็นอยู่ในปัจจุบันคำตอบนี้บอกว่า "อาจมีปัญหากับข้อเสนอนี้ฉันไม่แน่ใจว่าสิ่งที่ฉันเพิ่งพูดไปนั้นสมเหตุสมผลหรือไม่ (!) อย่าลังเลที่จะแสดงความคิดเห็น (!!); และ โอ้ว่ามันเป็นอย่างไร C ++ 17 จะทำงานอย่างไร "
Kyle Strand

1
@KyleStrand อ่าใช่นั่นเป็นอีกปัญหาหนึ่งที่ฉันสังเกตเห็น แต่ลืมพูดถึงความสนุกอื่น ๆ ทั้งหมด การแก้ไขเกี่ยวกับ C ++ 17 ไม่ได้ทำโดย OP ... และ IMO ไม่ควรได้รับการอนุมัติ แต่โพสต์เป็นคำตอบใหม่: จะถูกปฏิเสธได้เนื่องจาก 'การเปลี่ยนแปลงความหมายของโพสต์' แม้ว่าโพสต์จะมี ฉันไม่ทราบว่าการแก้ไขในส่วนใหม่ทั้งหมดเป็นเกมที่ยุติธรรมและแน่นอนว่ามีการปฏิเสธการแก้ไขที่รุนแรงน้อยกว่า แต่ฉันคิดว่านั่นเป็นโชคดีของการจับฉลากในแง่ของผู้ตรวจสอบที่คุณได้รับ
underscore_d

27

คุณไม่สามารถทำตามที่ขอด้วยเหตุผลที่คนอื่นพูดถึง แต่คุณสามารถทำได้:

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}

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

หมายเหตุ: คอมไพเลอร์ที่สมเหตุสมผลใด ๆ จะเพิ่มประสิทธิภาพวัตถุชั่วคราวเมื่อคุณเขียนสิ่งที่ต้องการ

auto v = make_variable(instance);

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

3
และดียิ่งขึ้นใน C ++ 11 คุณสามารถauto v = make_variable(instance)ทำได้โดยไม่ต้องระบุประเภทจริง ๆ
Claudiu

1
ใช่ฮ่า ๆ กับความคิดที่จะประกาศฟังก์ชัน make เป็นstaticสมาชิก ... ลองคิดดูสักครู่ ว่ากัน: ฟังก์ชั่นทำให้ฟรีจริง ๆวิธีการแก้ปัญหา แต่ก็เป็นจำนวนมากสำเร็จรูปซ้ำซ้อนว่าในขณะที่คุณกำลังพิมพ์คุณก็รู้ว่าคุณไม่ควรมีเพราะคอมไพเลอร์ที่มีการเข้าถึงทุกข้อมูลที่คุณกำลังทำซ้ำ .. และขอบคุณ C ++ 17 บัญญัติที่
underscore_d

21

ในยุคแห่งการรู้แจ้งของปี 2559 ด้วยมาตรฐานใหม่สองมาตรฐานภายใต้เข็มขัดของเราเนื่องจากคำถามนี้ถูกถามและคำถามใหม่ที่อยู่ใกล้ ๆ สิ่งสำคัญที่ต้องรู้คือคอมไพเลอร์ที่รองรับมาตรฐาน C ++ 17 จะรวบรวมโค้ดของคุณตามที่เป็นอยู่ .

การหักอาร์กิวเมนต์เทมเพลตสำหรับเทมเพลตคลาสใน C ++ 17

ที่นี่ (ได้รับความอนุเคราะห์จากการแก้ไขโดย Olzhas Zhumabek สำหรับคำตอบที่ยอมรับ) เป็นเอกสารที่มีรายละเอียดการเปลี่ยนแปลงที่เกี่ยวข้องกับมาตรฐาน

จัดการข้อกังวลจากคำตอบอื่น ๆ

คำตอบยอดนิยมในปัจจุบัน

คำตอบนี้ชี้ให้เห็นว่า "ตัวสร้างการคัดลอกและoperator=" ไม่ทราบถึงความเชี่ยวชาญพิเศษของเทมเพลตที่ถูกต้อง

นี่เป็นเรื่องไร้สาระเนื่องจากตัวสร้างสำเนามาตรฐานและoperator= มีอยู่สำหรับประเภทเทมเพลตที่รู้จักเท่านั้น:

template <typename T>
class MyClass {
    MyClass(const MyClass&) =default;
    ... etc...
};

// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm;   // WHAT IS THIS?
*pm = m;

ที่นี่ที่ผมตั้งข้อสังเกตในความคิดเห็นที่มีไม่มีเหตุผลสำหรับการMyClass *pmที่จะเป็นคำชี้แจงทางกฎหมายที่มีหรือไม่มีรูปแบบใหม่ของการอนุมาน: MyClass ไม่ได้เป็นประเภท (เป็นแม่แบบ) ดังนั้นจึงไม่ได้ทำให้ความรู้สึกที่จะประกาศตัวชี้ของ ประเภทMyClass. นี่เป็นวิธีหนึ่งที่เป็นไปได้ในการแก้ไขตัวอย่าง:

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

ที่นี่pmเป็นประเภทที่ถูกต้องอยู่แล้วดังนั้นการอนุมานจึงเป็นเรื่องเล็กน้อย ยิ่งไปกว่านั้นมันเป็นไปไม่ได้ที่จะผสมประเภทโดยบังเอิญเมื่อเรียกตัวสร้างสำเนา:

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));

ที่นี่pmจะเป็นตัวชี้ไปยังสำเนาของmไฟล์. ที่นี่MyClassกำลังสร้างสำเนาจากm- ซึ่งเป็นประเภทMyClass<string>( ไม่ใช่ประเภทที่ไม่มีอยู่MyClass) ดังนั้นที่จุดที่pmพิมพ์ 's อนุมานได้มีเป็นข้อมูลที่เพียงพอที่จะรู้ว่าแม่แบบชนิดของmและดังนั้นจึงแม่แบบประเภทของการเป็นpmstring

ยิ่งไปกว่านั้นสิ่งต่อไปนี้จะทำให้เกิดข้อผิดพลาดในการคอมไพล์เสมอ :

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

เนื่องจากการประกาศของตัวสร้างการคัดลอกไม่มีเทมเพลต:

MyClass(const MyClass&);

ที่นี่ประเภทเทมเพลตของอาร์กิวเมนต์ copy-constructor ตรงกับ template-type ของคลาสโดยรวม กล่าวคือเมื่อMyClass<string>มีการสร้างอินสแตนซ์MyClass<string>::MyClass(const MyClass<string>&);จะถูกสร้างอินสแตนซ์กับมันและเมื่อMyClass<int>ถูกสร้างอินสแตนซ์MyClass<int>::MyClass(const MyClass<int>&);จะถูกสร้างอินสแตนซ์ เว้นแต่จะมีการระบุไว้อย่างชัดเจนหรือมีการประกาศตัวสร้างเทมเพลตไม่มีเหตุผลใดที่คอมไพลเลอร์จะสร้างอินสแตนซ์MyClass<int>::MyClass(const MyClass<string>&);ซึ่งเห็นได้ชัดว่าไม่เหมาะสม

คำตอบโดยCătălin Pitiș

Pitişให้ตัวอย่าง deducing Variable<int>และVariable<double>แล้วสหรัฐฯ:

ฉันมีชื่อประเภทเดียวกัน (ตัวแปร) ในรหัสสำหรับสองประเภทที่แตกต่างกัน (ตัวแปรและตัวแปร) จากมุมมองส่วนตัวของฉันมันมีผลต่อการอ่านโค้ดค่อนข้างมาก

ที่ระบุไว้ในตัวอย่างก่อนหน้านี้Variableตัวเองเป็นไม่ได้ชื่อประเภทแม้ว่าคุณลักษณะใหม่ที่ทำให้มันมีลักษณะเหมือนหนึ่ง syntactically

จากนั้น Pitiș ก็ถามว่าจะเกิดอะไรขึ้นหากไม่มีการกำหนดตัวสร้างที่จะอนุญาตการอนุมานที่เหมาะสม คำตอบก็คือไม่มีการอนุมานได้รับอนุญาตเพราะการอนุมานจะถูกเรียกโดยโทรคอนสตรัค โดยไม่ต้องมีคอนสตรัคโทรมีไม่มีการอนุมาน

คล้ายกับการถามว่าfooมีการอนุมานเวอร์ชันใดที่นี่:

template <typename T> foo();
foo();

คำตอบคือรหัสนี้ผิดกฎหมายด้วยเหตุผลที่ระบุไว้

คำตอบของ MSalter

เท่าที่ฉันสามารถบอกได้คำตอบเดียวที่จะทำให้เกิดข้อกังวลที่ถูกต้องเกี่ยวกับคุณลักษณะที่เสนอ

ตัวอย่างคือ:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

คำถามสำคัญคือคอมไพลเลอร์เลือกตัวสร้างที่อนุมานประเภทที่นี่หรือตัวสร้างการคัดลอก ?

ลองใช้รหัสเราจะเห็นว่ามีการเลือกตัวสร้างการคัดลอก หากต้องการขยายตัวอย่าง :

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

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

ฉันก็ไม่แน่ใจเหมือนกันว่าทำไมการvar4หักเงินจึงผิดกฎหมาย ข้อผิดพลาดของคอมไพเลอร์จาก g ++ ดูเหมือนจะบ่งชี้ว่าคำสั่งกำลังถูกแยกวิเคราะห์เป็นการประกาศฟังก์ชัน


ช่างเป็นคำตอบที่ละเอียดและยอดเยี่ยมจริงๆ! var4เป็นเพียงกรณีของ "การแยกวิเคราะห์ที่น่ารำคาญที่สุด" (ไม่เกี่ยวข้องกับการหักข้อโต้แย้งเทมเพลต) เราเคยใช้เพียงแค่ใช้ parens พิเศษสำหรับสิ่งนี้ แต่วันนี้ฉันคิดว่าการใช้วงเล็บปีกกาเพื่อแสดงถึงการก่อสร้างเป็นคำแนะนำตามปกติ
Sumudu Fernando

@SumuduFernando ขอบคุณ! คุณหมายถึงVariable var4(Variable(num));ถือว่าเป็นการประกาศฟังก์ชันหรือไม่? ถ้าเป็นเช่นนั้นเหตุใดVariable(num)ข้อกำหนดพารามิเตอร์จึงถูกต้อง
Kyle Strand

@SumuduFernando ไม่เป็นไรฉันไม่รู้ว่ามันถูกต้อง: coliru.stacked-crooked.com/a/98c36b8082660941
Kyle Strand

11

ยังคงหายไป: ทำให้รหัสต่อไปนี้ค่อนข้างคลุมเครือ:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

อีกจุดที่ดี สมมติว่ามีตัวสร้างสำเนาที่กำหนดตัวแปร (Variable <obj> d) จะต้องมีการสร้างลำดับความสำคัญบางอย่าง
GRB

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

นี่เป็นประเด็นที่น่าสนใจและ (ตามที่ฉันได้ระบุไว้ในคำตอบของฉัน) ฉันยังไม่แน่ใจว่าข้อเสนอ C ++ 17 ที่ยอมรับจะแก้ไขปัญหานี้ได้อย่างไร
Kyle Strand

9

สมมติว่าคอมไพเลอร์สนับสนุนสิ่งที่คุณถาม รหัสนี้ถูกต้อง:

Variable v1( 10); // Variable<int>

// Some code here

Variable v2( 20.4); // Variable<double>

ตอนนี้ฉันมีชื่อประเภทเดียวกัน (ตัวแปร) ในรหัสสำหรับสองประเภทที่แตกต่างกัน (ตัวแปรและตัวแปร) จากมุมมองส่วนตัวของฉันมันมีผลต่อการอ่านโค้ดค่อนข้างมาก การมีชื่อประเภทเดียวกันสำหรับสองประเภทที่แตกต่างกันในเนมสเปซเดียวกันทำให้ฉันเข้าใจผิด

การอัปเดตในภายหลัง: สิ่งที่ควรพิจารณาอีกประการหนึ่ง: ความเชี่ยวชาญพิเศษของเทมเพลตบางส่วน (หรือทั้งหมด)

จะเป็นอย่างไรหากฉันเชี่ยวชาญ Variable และไม่มีตัวสร้างอย่างที่คุณคาดหวัง

ดังนั้นฉันจะมี:

template<>
class Variable<int>
{
// Provide default constructor only.
};

จากนั้นฉันมีรหัส:

Variable v( 10);

คอมไพเลอร์ควรทำอย่างไร? ใช้นิยามคลาสตัวแปรทั่วไปเพื่ออนุมานว่าเป็นตัวแปรแล้วค้นพบว่าตัวแปรไม่ได้ให้ตัวสร้างพารามิเตอร์เดียว?


1
แย่กว่านั้น: จะเกิดอะไรขึ้นถ้าคุณมี Variable <int> :: Variable (float)? ตอนนี้คุณมีสองวิธีในการอนุมานตัวแปร (1f) และไม่มีวิธีอนุมานตัวแปร (1)
MSalters

เป็นจุดที่ดี แต่สามารถเอาชนะได้อย่างง่ายดายด้วยการแคสต์: Variable v1 ((double) 10)
jpinto3912

ฉันยอมรับว่าความสามารถในการอ่านโค้ดเป็นปัญหาส่วนตัวอย่างไรก็ตามฉันเห็นด้วย 100% กับสิ่งที่คุณพูดเกี่ยวกับความเชี่ยวชาญพิเศษของเทมเพลต วิธีแก้ปัญหาอาจเป็นการให้ข้อผิดพลาดพารามิเตอร์เทมเพลตที่ไม่ได้กำหนด (เมื่อคอมไพเลอร์ดูความเชี่ยวชาญพิเศษ <int> และไม่เห็นตัวสร้างที่ถูกต้องบอกว่าไม่รู้ว่าคุณต้องการใช้เทมเพลตใดและคุณต้องระบุอย่างชัดเจน) แต่ ฉันยอมรับว่ามันไม่ใช่ทางออกที่สวยหรู ฉันจะเพิ่มสิ่งนี้เป็นช่องทางไวยากรณ์ที่สำคัญอีกอันที่ต้องจัดการ (แต่สามารถแก้ไขได้หากยอมรับผลที่ตามมา)
GRB

4
@ jpinto3912 - คุณกำลังพลาดประเด็นนี้ คอมไพเลอร์ต้องสร้างอินสแตนซ์ตัวแปร <T> ที่เป็นไปได้ทั้งหมดเพื่อตรวจสอบว่าตัวแปร ctor <T> :: ตัวแปรใด ๆ ให้ ctor คลุมเครือหรือไม่ การกำจัดความคลุมเครือไม่ใช่ปัญหา - สร้างอินสแตนซ์ตัวแปร <double> ง่ายๆด้วยตัวคุณเองหากนั่นคือสิ่งที่คุณต้องการ พบความคลุมเครือในตอนแรกซึ่งทำให้เป็นไปไม่ได้
MSalters

6

มาตรฐาน C ++ 03 และ C ++ 11 ไม่อนุญาตให้มีการหักอาร์กิวเมนต์เทมเพลตจากพารามิเตอร์ที่ส่งไปยังตัวสร้าง

แต่มีข้อเสนอสำหรับ "การหักค่าพารามิเตอร์เทมเพลตสำหรับตัวสร้าง" ดังนั้นคุณอาจได้รับสิ่งที่ต้องการในไม่ช้า แก้ไข: แน่นอนคุณสมบัตินี้ได้รับการยืนยันสำหรับ C ++ 17

ดู: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.htmlและhttp://www.open-std.org/jtc1/sc22/wg21/docs/ เอกสาร / 2015 / p0091r0.html


คุณลักษณะนี้ได้ถูกเพิ่มเข้าไปใน C ++ 17 แล้ว แต่จะไม่ใช่หาก "เร็ว ๆ นี้" ใช้กับกรอบเวลา 6 ถึง 8 ปี ;)
ChetS

2

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

หากคุณต้องการการอนุมานเทมเพลตจริงๆให้ใช้ฟังก์ชันตัวช่วย:

template<typename obj>
class Variable 
{
      obj data;
public: 
      Variable(obj d)
      : data(d)
      { }
};

template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
    return Variable<obj>(d);
}

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

1

การหักประเภทจะ จำกัด เฉพาะฟังก์ชันเทมเพลตใน C ++ ปัจจุบัน แต่เป็นที่ทราบกันมานานแล้วว่าการหักประเภทในบริบทอื่น ๆ จะมีประโยชน์มาก ดังนั้น autoC

ในขณะที่ว่าสิ่งที่คุณแนะนำจะไม่เป็นไปใน C ++ 0x แสดงให้เห็นต่อไปนี้คุณจะได้รับสวยใกล้เคียง:

template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
    // remove reference required for the case that x is an lvalue
    return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}

void test()
{
    auto v = MakeVariable(2); // v is of type Variable<int>
}

0

คุณคิดถูกที่คอมไพเลอร์สามารถเดาได้ง่าย แต่มันไม่ได้อยู่ในมาตรฐานหรือ C ++ 0x เท่าที่ฉันรู้ดังนั้นคุณจะต้องรออย่างน้อยอีก 10 ปี (มาตรฐาน ISO คงที่อัตราการหมุนเวียน) ก่อนที่ผู้ให้บริการคอมไพเลอร์จะเพิ่มคุณสมบัตินี้


ไม่ถูกต้องกับมาตรฐานที่กำลังจะมาถึงจะมีการนำคำหลักอัตโนมัติมาใช้ ดูโพสต์ของ James Hopkins ในหัวข้อนี้ stackoverflow.com/questions/984394/… . เขาแสดงให้เห็นว่ามันจะเป็นไปได้อย่างไรใน C ++ 0x
ovanes

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

ดูเหมือนว่าจะเป็นเวลา 8 ปีแล้ว (นับจากคำตอบนี้) ... 10 ปีก็ไม่ใช่เรื่องที่น่าเดาแม้ว่าในเวลานั้นจะมีสองมาตรฐานก็ตาม!
Kyle Strand

-1

ลองดูปัญหาที่อ้างอิงถึงคลาสที่ทุกคนควรรู้จักด้วย - std :: vector

ประการแรกการใช้เวกเตอร์โดยทั่วไปคือการใช้ตัวสร้างที่ไม่มีพารามิเตอร์:

vector <int> v;

ในกรณีนี้ไม่สามารถทำการอนุมานได้อย่างชัดเจน

การใช้งานทั่วไปครั้งที่สองคือการสร้างเวกเตอร์ขนาดล่วงหน้า:

vector <string> v(100);

ที่นี่หากใช้การอนุมาน:

vector v(100);

เราได้เวกเตอร์ของ ints ไม่ใช่สตริงและน่าจะไม่ได้ขนาด!

สุดท้ายให้พิจารณาตัวสร้างที่รับพารามิเตอร์หลายตัว - ด้วย "การอนุมาน":

vector v( 100, foobar() );      // foobar is some class

ควรใช้พารามิเตอร์ใดในการอนุมาน เราต้องการวิธีบอกคอมไพเลอร์ว่าควรเป็นคอมไพเลอร์ตัวที่สอง

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


3
ฉันคิดว่าคุณเข้าใจผิดในความคิดนี้ การอนุมานประเภทสำหรับตัวสร้างจะเกิดขึ้นก็ต่อเมื่อประเภทเทมเพลตเป็นส่วนหนึ่งของตัวสร้าง สมมติว่าเวกเตอร์มีเทมเพลตข้อกำหนดเทมเพลต <typename T> ตัวอย่างของคุณไม่ใช่ปัญหาเนื่องจากตัวสร้างเวกเตอร์จะถูกกำหนดเป็นเวกเตอร์ (ขนาด int) ไม่ใช่เวกเตอร์ (ขนาด T) เฉพาะในกรณีของเวกเตอร์ (ขนาด T) เท่านั้นที่จะเกิดการอนุมานได้ ในตัวอย่างแรกคอมไพเลอร์จะแจ้งข้อผิดพลาดว่า T ไม่ได้กำหนด โดยพื้นฐานแล้วจะเหมือนกับการอนุมานเทมเพลตฟังก์ชัน
GRB

ดังนั้นจึงจะเกิดขึ้นสำหรับตัวสร้างที่มีพารามิเตอร์เดียวเท่านั้นและพารามิเตอร์นั้นเป็นประเภทพารามิเตอร์เทมเพลตหรือไม่? ดูเหมือนว่าจะมีจำนวนน้อยนิดที่หายไป

ไม่จำเป็นต้องเป็นพารามิเตอร์เดียว ตัวอย่างเช่นอาจมีตัวสร้างเวกเตอร์ของเวกเตอร์ (ขนาด int, T firstElement) หากเทมเพลตมีหลายพารามิเตอร์ (template <typename T, typename U>) อาจมี Holder :: Holder (T firstObject, U secondObject) หากเทมเพลตมีหลายพารามิเตอร์ แต่ตัวสร้างใช้เพียงตัวเดียวเช่น Holder (U secondObject) T จะต้องระบุไว้อย่างชัดเจนเสมอ กฎจะมีจุดมุ่งหมายให้คล้ายกับการอนุมานเทมเพลตฟังก์ชันมากที่สุด
GRB

-2

การทำให้ ctor เป็นเทมเพลตตัวแปรสามารถมีได้เพียงรูปแบบเดียวแต่ ctors ต่างกัน:

class Variable {
      obj data; // let the compiler guess
      public:
      template<typename obj>
      Variable(obj d)
       {
           data = d;
       }
};

int main()
{
    int num = 2;
    Variable var(num);  // Variable::data int?

    float num2 = 2.0f;
    Variable var2(num2);  // Variable::data float?
    return 0;         
}

ดู? เราไม่สามารถมีสมาชิก Variable :: data ได้หลายตัว


นั่นจะไม่สมเหตุสมผลภายใต้สถานการณ์ใด ๆ obj ในแง่ของข้อมูล obj ไม่ได้กำหนดไว้เนื่องจากคลาสนั้นไม่ใช่เทมเพลตอีกต่อไป รหัสดังกล่าวจะไม่ถูกต้องไม่ว่าจะด้วยวิธีใดก็ตาม
GRB

ฉันต้องการพฤติกรรมของคอมไพเลอร์ที่คุณอธิบายดังนั้นฉันจึงหาวิธีข้ามข้อ จำกัด นั้น (ในกรณีของฉัน) ซึ่งคุณอาจพบว่าน่าสนใจstackoverflow.com/questions/228620/garbage-collection-in-c-why/…
Nick Dandoulakis

-2

ดูการหักอาร์กิวเมนต์เทมเพลต C ++สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้


4
ฉันอ่านบทความนี้มาก่อนและดูเหมือนจะไม่ได้พูดถึงสิ่งที่ฉันพูดมากนัก ครั้งเดียวที่ผู้เขียนดูเหมือนจะพูดถึงการหักข้อโต้แย้งเกี่ยวกับชั้นเรียนคือเมื่อเขาบอกว่าไม่สามารถทำได้ที่ด้านบนของบทความ;) - ถ้าคุณสามารถชี้ให้เห็นส่วนที่คุณคิดว่าเกี่ยวข้องแม้ว่าฉันจะ ' d ขอบคุณจริงๆ
GRB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.