อาร์กิวเมนต์แม่แบบเริ่มต้นสำหรับแม่แบบฟังก์ชัน


187

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

struct mycclass {
  template<class T=int>
  void mymember(T* vec) {
    // ...
  }
};

แต่ C ++ จะบังคับให้อาร์กิวเมนต์แม่แบบเริ่มต้นนั้นได้รับอนุญาตในแม่แบบคลาสเท่านั้น


8
+1 นั่นเป็นคำถามที่ยุ่งยากจริงๆ
AraK

1
สำหรับสามคำตอบที่โพสต์แรกให้พิจารณาตัวอย่างนี้: struct S { template <class R = int> R get_me_R() { return R(); } };ไม่สามารถอนุมานพารามิเตอร์เทมเพลตจากบริบทได้
AraK

3
คำถามที่ดี. 3 คนตอบคำถามแล้วว่า "ไม่สมเหตุสมผล" และพวกเขาทั้งหมดผิดปกติ พารามิเตอร์เทมเพลตของฟังก์ชั่นไม่สามารถหักจากพารามิเตอร์การเรียกใช้ฟังก์ชัน ตัวอย่างเช่นถ้าพวกเขาได้รับอนุญาตให้ฉันสามารถเขียนtemplate <int N = 1> int &increment(int &i) { i += N; return i; }แล้วหรือincrement(i); มันเป็นผมต้องเขียนincrement<2>(i); increment<1>(i);
Steve Jessop

ที่จริงแล้วตัวอย่างของ Mine และ AraK สามารถจัดการได้โดยการบรรทุกเกินพิกัด ฉันคิดว่า litb ไม่สามารถทำได้เพราะพารามิเตอร์เทมเพลตอาจจะอนุมานได้หรืออาจระบุไว้ก็ได้
Steve Jessop

3
@Steve: เซมิโคลอนที่หายไปเป็นจริงเกินตัวดำเนินการ EOL ใหม่เพื่อเสริม B. Stavtrup ของ "การบรรทุกเกินพิกัดของ C ++ Whitespace" ตีพิมพ์ในวารสารของการเขียนโปรแกรมเชิงวัตถุ, 1 เมษายน 1992 ( www2.research.att.com/~bs/) papers.html )

คำตอบ:


148

มันเหมาะสมที่จะให้ข้อโต้แย้งแม่แบบเริ่มต้น ตัวอย่างเช่นคุณสามารถสร้างฟังก์ชั่นการจัดเรียง:

template<typename Iterator, 
         typename Comp = std::less<
            typename std::iterator_traits<Iterator>::value_type> >
void sort(Iterator beg, Iterator end, Comp c = Comp()) {
  ...
}

C ++ 0x แนะนำให้รู้จักกับ C ++ ดูรายงานข้อบกพร่องนี้โดย Bjarne Stroustrup: อาร์กิวเมนต์เทมเพลตเริ่มต้นสำหรับเทมเพลตฟังก์ชันและสิ่งที่เขาพูด

ข้อห้ามของอาร์กิวเมนต์เท็มเพลตเริ่มต้นสำหรับเท็มเพลตฟังก์ชั่นเป็นเศษเล็กเศษน้อยที่ผิดพลาดของเวลาที่ฟังก์ชั่นอิสระได้รับการปฏิบัติในฐานะพลเมืองชั้นสองและต้องการให้อาร์กิวเมนต์เท็มเพลตทั้งหมดถูกอนุมานจากฟังก์ชันอาร์กิวเมนต์

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


@Arman ลิงก์รายงานข้อบกพร่องมีการเปลี่ยนแปลงที่ทำกับแบบร่างการทำงานสำหรับ C ++ 0x และการสนทนา อาร์กิวเมนต์ที่ไม่ได้อนุมานหรือระบุอย่างชัดเจนนั้นได้มาจากอาร์กิวเมนต์เริ่มต้น GCC4.4 สนับสนุนอาร์กิวเมนต์เริ่มต้นสำหรับเทมเพลตฟังก์ชันในโหมด C ++ 0x
Johannes Schaub - litb

4
ไม่มีอะไรเกี่ยวข้องกับคำถามหรือคำตอบ แต่ Herb Sutter เรียกว่ามาตรฐาน C ++ 11 ที่อัปเดตหลังจากการประชุมวันเสาร์ที่แล้ว ฉันเพิ่งอ่านวันนี้และรู้สึกอยากแบ่งปัน :) herbutter.wordpress.com/2010/03/13/ …
David Rodríguez - dribeas

และติดตามผลบังคับคำถาม ... เมื่ออยู่ในนี้คาดว่าจะทำให้มันเข้าไปในคอมไพเลอร์อื่น ๆ :)
เจมี่คุก

@ JohannesSchaub-litb ฉันมีปัญหาเดียวกัน: ไม่มีความเป็นไปได้ที่จะตีพิมพ์ประเภทเริ่มต้นในฟังก์ชันแม่แบบ ฉันแก้ไขโดยการสร้างอินสแตนซ์ของฟังก์ชั่นที่เป็นค่าเริ่มต้นอย่างชัดเจน ( doubleในกรณีของฉัน) บางทีมันอาจจะไม่ใช่ "ทั่วไป" แต่มีข้อเสียเปรียบในการฝึกนี้หรือไม่? ขอบคุณ
JackOLantern

รหัสต่อไปนี้ล้มเหลวในการรวบรวมมีข้อผิดพลาดเช่นerror: invalid conversion from ‘int’ to ‘int*’ความคิดใด ๆ ว่าทำไม: `#include <array> #include <algorithm> #include แม่แบบ <functional> แม่แบบ <typename Iterator, typename Comp = std :: less <Iterator> โมฆะ my_sort ( Iterator beg, Iterator end, Comp c = Comp ()) {std :: sort (beg, end, c); } int main () {std :: array <int, 5> ar {5,2,21,7,4} my_sort (ar.begin (), ar.end ()); } `
Luke Peterson

36

วิธีอ้างอิงเทมเพลต C ++: คู่มือฉบับสมบูรณ์ (หน้า 207):

เมื่อเทมเพลตถูกเพิ่มเข้ามาในภาษา C ++ ข้อโต้แย้งเท็มเพลตฟังก์ชันที่ชัดเจนนั้นไม่ใช่โครงสร้างที่ถูกต้อง อาร์กิวเมนต์เทมเพลตของฟังก์ชั่นจะต้องสามารถอนุมานได้จากนิพจน์การโทร เป็นผลให้ดูเหมือนจะไม่มีเหตุผลที่น่าสนใจที่จะอนุญาตให้มีข้อโต้แย้งแม่แบบฟังก์ชั่นเริ่มต้นเพราะค่าเริ่มต้นจะถูกแทนที่โดยค่าที่หัก


เรียบง่ายและกระชับ :)
InQusitive

17

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

อารักษ์:

struct S { 
    template <class R = int> R get_me_R() { return R(); } 
};

อาจจะเป็น:

struct S {
    template <class R> R get_me_R() { return R(); } 
    int get_me_R() { return int(); }
};

ของฉันเอง:

template <int N = 1> int &increment(int &i) { i += N; return i; }

อาจจะเป็น:

template <int N> int &increment(int &i) { i += N; return i; }
int &increment(int &i) { return increment<1>(i); }

litb:

template<typename Iterator, typename Comp = std::less<Iterator> >
void sort(Iterator beg, Iterator end, Comp c = Comp())

อาจจะเป็น:

template<typename Iterator>
void sort(Iterator beg, Iterator end, std::less<Iterator> c = std::less<Iterator>())

template<typename Iterator, typename Comp >
void sort(Iterator beg, Iterator end, Comp c = Comp())

Stroustrup:

template <class T, class U = double>
void f(T t = 0, U u = 0);

อาจจะเป็น:

template <typename S, typename T> void f(S s = 0, T t = 0);
template <typename S> void f(S s = 0, double t = 0);

ซึ่งฉันพิสูจน์ด้วยรหัสต่อไปนี้:

#include <iostream>
#include <string>
#include <sstream>
#include <ctype.h>

template <typename T> T prettify(T t) { return t; }
std::string prettify(char c) { 
    std::stringstream ss;
    if (isprint((unsigned char)c)) {
        ss << "'" << c << "'";
    } else {
        ss << (int)c;
    }
    return ss.str();
}

template <typename S, typename T> void g(S s, T t){
    std::cout << "f<" << typeid(S).name() << "," << typeid(T).name()
        << ">(" << s << "," << prettify(t) << ")\n";
}


template <typename S, typename T> void f(S s = 0, T t = 0){
    g<S,T>(s,t);
}

template <typename S> void f(S s = 0, double t = 0) {
    g<S,double>(s, t);
}

int main() {
        f(1, 'c');         // f<int,char>(1,'c')
        f(1);              // f<int,double>(1,0)
//        f();               // error: T cannot be deduced
        f<int>();          // f<int,double>(0,0)
        f<int,char>();     // f<int,char>(0,0)
}

เอาต์พุตที่พิมพ์ออกมาตรงกับความคิดเห็นสำหรับการโทรแต่ละครั้งถึง f และการคอมเม้นต์การโทรออกไม่สามารถคอมไพล์ได้ตามที่คาดไว้

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


@ Steve: ไข่วิ่งเร็วกว่าไก่เหรอ? :) น่าสนใจ ขอบคุณ
Arman

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

2
อีกหนึ่งสำหรับคุณ: template<typename T = void> int SomeFunction();. แม่แบบพารามิเตอร์ที่นี่ไม่เคยใช้และในความเป็นจริงฟังก์ชั่นไม่เคยเรียก; สถานที่เดียวที่มันจะเรียกว่าอยู่ในหรือdecltype sizeofชื่อจงใจตรงกับชื่อของฟังก์ชั่นอื่น แต่ความจริงมันเป็นแม่แบบหมายความว่าคอมไพเลอร์จะชอบฟังก์ชั่นฟรีถ้ามันมีอยู่ ทั้งสองถูกใช้ใน SFINAE เพื่อจัดเตรียมพฤติกรรมเริ่มต้นโดยที่การกำหนดฟังก์ชันหายไป
Tom

4

ใน Windows ด้วย Visual Studio ทุกรุ่นคุณสามารถแปลงข้อผิดพลาดนี้ ( C4519 ) เป็นคำเตือนหรือปิดการใช้งานได้เช่น:

#ifdef  _MSC_VER
#pragma warning(1 : 4519) // convert error C4519 to warning
// #pragma warning(disable : 4519) // disable error C4519
#endif

ดูรายละเอียดเพิ่มเติมที่นี่


1
โปรดทราบว่าแม้ว่าสิ่งนี้จะปิดใช้งานข้อความ "แม่แบบเริ่มต้นที่ได้รับอนุญาตในแม่แบบคลาสเท่านั้น" แต่จริงๆแล้วมันไม่ได้ทำให้กระบวนการสร้างอินสแตนซ์แม่แบบนั้นใช้ค่าที่ให้มา ที่ต้องมี VS2013 (หรือคอมไพเลอร์อื่น ๆ ที่ได้เสร็จสิ้น C ++ 11 ข้อบกพร่อง 226 "อาร์กิวเมนต์แม่แบบเริ่มต้นสำหรับแม่แบบฟังก์ชั่น")
puetzk

1

สิ่งที่ฉันใช้คือเคล็ดลับถัดไป:

ให้บอกว่าคุณต้องการมีฟังก์ชั่นเช่นนี้:

template <typename E, typename ARR_E = MyArray_t<E> > void doStuff(ARR_E array)
{
    E one(1);
    array.add( one );
}

คุณจะไม่ได้รับอนุญาต แต่ฉันทำวิธีต่อไป:

template <typename T>
struct MyArray_t {
void add(T i) 
{
    // ...
}
};

template <typename E, typename ARR_E = MyArray_t<E> >
class worker {
public:
    /*static - as you wish */ ARR_E* parr_;
    void doStuff(); /* do not make this one static also, MSVC complains */
};

template <typename E, typename ARR_E>
void worker<E, ARR_E>::doStuff()
{
    E one(1);
    parr_->add( one );
}

ด้วยวิธีนี้คุณอาจใช้มันในลักษณะนี้:

MyArray_t<int> my_array;
worker<int> w;
w.parr_ = &arr;
w.doStuff();

ในขณะที่เราสามารถเห็นไม่จำเป็นต้องตั้งค่าพารามิเตอร์ที่สองอย่างชัดเจน บางทีมันอาจจะมีประโยชน์สำหรับใครบางคน


นี่ไม่ใช่คำตอบที่แน่นอน
ลูกสุนัข

@deadmg - คุณอธิบายได้ไหม พวกเราไม่ใช่ผู้เชี่ยวชาญด้านเทมเพลต C ++ ขอบคุณ
Kev

นี่เป็นวิธีแก้ปัญหาที่ค่อนข้างเรียบร้อย แต่จะไม่ครอบคลุมทุกกรณีที่คุณต้องการ ตัวอย่างเช่นคุณจะใช้สิ่งนี้กับตัวสร้างได้อย่างไร
Tiberiu Savin

@TiberiuSavin - ถ้าฉันเข้าใจคุณถูกต้องคุณสามารถทำได้ดังนี้: template <typename E, typename ARR_E> ผู้ปฏิบัติงาน <E, ARR_E> :: คนงาน (ARR_E * parr) {parr_ = parr; } จากนั้นใช้อย่างนี้: ผู้ปฏิบัติงาน <int> w2 (& my_array);
alariq
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.