ข้อผิดพลาดของเทมเพลตที่สับสน


91

ฉันเล่นกับเสียงดังมาระยะหนึ่งแล้วและฉันก็สะดุดกับ "test / SemaTemplate / depend-template-recover.cpp" (ในการแจกแจงเสียงดัง) ซึ่งควรให้คำแนะนำในการกู้คืนจากข้อผิดพลาดของเทมเพลต

สิ่งทั้งหมดสามารถถอดออกเป็นตัวอย่างเล็กน้อยได้อย่างง่ายดาย:

template<typename T, typename U, int N> struct X {
    void f(T* t)
    {
        // expected-error{{use 'template' keyword to treat 'f0' as a dependent template name}}
        t->f0<U>();
    }
};

ข้อความแสดงข้อผิดพลาดที่เกิดจากเสียงดังลั่น:

tpl.cpp:6:13: error: use 'template' keyword to treat 'f0' as a dependent template name
         t->f0<U>();
            ^
            template 
1 error generated.

... แต่ฉันมีความเข้าใจยากว่าควรจะแทรกtemplateคีย์เวิร์ดตรงไหนเพื่อให้โค้ดถูกต้องตามหลักไวยากรณ์?


11
คุณลองแทรกตรงที่ลูกศรชี้หรือไม่?
Mike Seymour

3
คล้ายกับสิ่งนี้และสิ่งนี้
Prasoon Saurav

คำตอบ:


104

ISO C ++ 03 14.2 / 4:

เมื่อชื่อของความเชี่ยวชาญเทมเพลตสมาชิกปรากฏขึ้นหลัง หรือ -> ใน postfix-expression หรือหลังตัวระบุชื่อที่ซ้อนกันใน ID ที่ผ่านการรับรองและ postfix-expression หรือ qualit-id ขึ้นอยู่กับ template-parameters อย่างชัดเจน (14.6.2) ชื่อเทมเพลตสมาชิกต้องเป็น นำหน้าด้วยแม่แบบคำหลัก มิฉะนั้นชื่อจะถือว่าเป็นชื่อที่ไม่ใช่เทมเพลต

In t->f0<U>(); f0<U>คือความเชี่ยวชาญเทมเพลตสมาชิกซึ่งจะปรากฏขึ้นตามมา->และขึ้นอยู่กับพารามิเตอร์เทมเพลตอย่างชัดเจนUดังนั้นความเชี่ยวชาญเทมเพลตสมาชิกจะต้องนำหน้าด้วยtemplateคำสำคัญ

ดังนั้นเปลี่ยนt->f0<U>()เป็นt->template f0<U>().


ที่น่าสนใจคือฉันคิดว่าการใส่นิพจน์ในวงเล็บ: t->(f0<U>())จะได้รับการแก้ไขอย่างที่ฉันคิดว่าจะนำf0<U>()ไปสู่นิพจน์แบบสแตนด์อโลน ... ฉันคิดผิดดูเหมือนว่า ...

26
คุณอาจแสดงความคิดเห็นว่าเหตุใดจึงเป็นเช่นนั้น เหตุใด C ++ จึงต้องการไวยากรณ์ประเภทนี้
Curious

2
ใช่นี่เป็นเรื่องแปลก ภาษาสามารถ "ตรวจจับ" ได้ว่าต้องมีคีย์เวิร์ดของเทมเพลต หากทำได้ก็ควร "แทรก" คีย์เวิร์ดในนั้นเอง
Enrico Borba

26

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

#include <iostream>

template<typename T>
struct A {
  typedef int R();

  template<typename U>
  static U *f(int) { 
    return 0; 
  }

  static int f() { 
    return 0;
  }
};

template<typename T>
bool g() {
  A<T> a;
  return !(typename A<T>::R*)a.f<int()>(0);
}


int main() {
  std::cout << g<void>() << std::endl;
}

สิ่งนี้จะพิมพ์0เมื่อเว้นไว้templateก่อนหน้าf<int()>แต่1เมื่อใส่เข้าไป ฉันปล่อยให้มันเป็นแบบฝึกหัดเพื่อหาว่าโค้ดทำอะไร


3
ตอนนี้เป็นตัวอย่างที่ชั่วร้าย!
Matthieu M.

1
ฉันไม่สามารถสร้างพฤติกรรมที่คุณกำลังอธิบายใน Visual Studio 2013 ได้มันมักจะเรียกf<U>และพิมพ์เสมอ1ซึ่งเหมาะสมกับฉันมาก ฉันยังไม่เข้าใจว่าทำไมtemplateต้องใช้คีย์เวิร์ดและทำให้เกิดความแตกต่างอย่างไร
ยีราฟสีม่วง

@Violet คอมไพเลอร์ VSC ++ ไม่ใช่คอมไพเลอร์ C ++ ที่สอดคล้อง จำเป็นต้องมีคำถามใหม่หากคุณต้องการทราบว่าทำไม VSC ++ จึงพิมพ์ 1.
Johannes Schaub - litb

1
คำตอบนี้อธิบายว่าเหตุใดจึงtemplateจำเป็น: stackoverflow.com/questions/610245/…โดยไม่ต้องอาศัยคำศัพท์มาตรฐานที่เข้าใจยากเพียงอย่างเดียว โปรดรายงานหากมีสิ่งใดในคำตอบนั้นที่ยังสับสน
Johannes Schaub -

@ JohannesSchaub-litb: ขอบคุณคำตอบที่ดี ปรากฎว่าฉันได้อ่านมาก่อนเพราะฉันได้รับการโหวตแล้ว เห็นได้ชัดว่าความทรงจำของฉันคือฉัน
ยีราฟสีม่วง

12

ใส่ไว้ก่อนจุดที่คาเร็ตอยู่:

template<typename T, typename U, int N> struct X {
     void f(T* t)
     {
        t->template f0<U>();
     }
};

แก้ไข: เหตุผลของกฎนี้จะชัดเจนขึ้นหากคุณคิดเหมือนคอมไพเลอร์ โดยทั่วไปคอมไพเลอร์จะมองไปข้างหน้าเพียงหนึ่งหรือสองโทเค็นในครั้งเดียวและโดยทั่วไปจะไม่ "มองไปข้างหน้า" กับส่วนที่เหลือของนิพจน์ [แก้ไข: ดูความคิดเห็น] เหตุผลของคีย์เวิร์ดนั้นเหมือนกับสาเหตุที่คุณต้องใช้typenameคีย์เวิร์ดเพื่อระบุชื่อประเภทที่อ้างอิง: มันกำลังบอกคอมไพเลอร์ว่า "เฮ้ตัวระบุที่คุณกำลังจะเห็นคือชื่อของเทมเพลตแทนที่จะเป็น ชื่อของสมาชิกข้อมูลแบบคงที่ตามด้วยเครื่องหมายน้อยกว่า "


1
ฉันจะไม่สามารถเดาได้เลย ... แต่ขอบคุณ ;-) มีบางสิ่งที่ต้องเรียนรู้เกี่ยวกับ C ++ อยู่เสมอ!

3
templateถึงแม้จะมีอนันต์มองไปข้างหน้าคุณจะยังคงต้อง มีหลายกรณีที่ทั้งแบบมีและไม่มีtemplateจะให้โปรแกรมที่ใช้ได้ซึ่งมีลักษณะการทำงานที่แตกต่าง ดังนั้นนี่จึงไม่เพียง แต่เป็นปัญหาทางไวยากรณ์เท่านั้น ( t->f0<int()>(0)เป็นไวยากรณ์ที่ถูกต้องสำหรับทั้งเวอร์ชันรายการอาร์กิวเมนต์น้อยกว่าและเทมเพลต)
Johannes Schaub - litb

@Johannes Schaub - litb: ใช่แล้วจึงเป็นปัญหาในการกำหนดความหมายที่สอดคล้องกันให้กับนิพจน์มากกว่าการมองไปข้างหน้า
Doug

11

ตัดตอนมาจากเทมเพลต C ++

.template Construct ปัญหาที่คล้ายกันมากถูกค้นพบหลังจากมีการใช้ชื่อพิมพ์ พิจารณาตัวอย่างต่อไปนี้โดยใช้ประเภทบิตเซ็ตมาตรฐาน:

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 

โครงสร้างแปลก ๆ ในตัวอย่างนี้คือ. เทมเพลต หากไม่มีการใช้เทมเพลตเพิ่มเติมคอมไพลเลอร์จะไม่รู้ว่าโทเค็นน้อยกว่า (<) ที่ตามมานั้นไม่ "น้อยกว่า" จริงๆ แต่เป็นจุดเริ่มต้นของรายการอาร์กิวเมนต์เทมเพลต โปรดทราบว่านี่เป็นปัญหาเฉพาะในกรณีที่โครงสร้างก่อนช่วงเวลาขึ้นอยู่กับพารามิเตอร์เทมเพลต ในตัวอย่างของเราพารามิเตอร์ bs ขึ้นอยู่กับพารามิเตอร์เทมเพลต N

สรุปได้ว่าควรใช้สัญกรณ์. template (และสัญกรณ์ที่คล้ายกันเช่น -> template) เฉพาะในเทมเพลตและถ้าเป็นไปตามสิ่งที่ขึ้นอยู่กับพารามิเตอร์ template เท่านั้น

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