เหตุใดฉันจึงใช้ค่าลอยเป็นพารามิเตอร์เทมเพลตไม่ได้


120

เมื่อฉันพยายามใช้floatเป็นพารามิเตอร์เทมเพลตคอมไพลเลอร์จะร้องหารหัสนี้ในขณะที่intทำงานได้ดี

เป็นเพราะฉันไม่สามารถใช้floatเป็นพารามิเตอร์เทมเพลตได้หรือไม่?

#include<iostream>
using namespace std;

template <class T, T defaultValue>
class GenericClass
{
private:
    T value;
public:
    GenericClass()
    {
        value = defaultValue;
    }

    T returnVal()
    {
        return value;
    }
}; 


int main()
{
    GenericClass <int, 10> gcInteger;
    GenericClass < float, 4.6f> gcFlaot;

    cout << "\n sum of integer is "<<gcInteger.returnVal();
    cout << "\n sum of float is "<<gcFlaot.returnVal();

    return 0;       
}

ข้อผิดพลาด:

main.cpp: In function `int main()':
main.cpp:25: error: `float' is not a valid type for a template constant parameter
main.cpp:25: error: invalid type in declaration before ';' token

main.cpp:28: error: request for member `returnVal' in `gcFlaot',
                    which is of non-class type `int'

ฉันกำลังอ่าน"โครงสร้างข้อมูลสำหรับโปรแกรมเมอร์เกม"ของรอนเพนตันผู้เขียนผ่าน a floatแต่เมื่อฉันลองมันดูเหมือนจะไม่รวบรวม


1
ผู้เขียนใช้floatเป็นพารามิเตอร์เทมเพลตที่ไม่ใช่ประเภทหรือไม่? ในบทนั้นคืออะไร?
K-ballo

1
พบมันอยู่ที่"การใช้ค่าเป็นพารามิเตอร์แม่แบบ" ...
K-ballo

คำตอบ:


37

ปัจจุบัน c ++ มาตรฐานไม่อนุญาตfloat(เช่นจำนวนจริง) หรือสายอักขระตัวอักษรที่จะใช้เป็นแม่แบบพารามิเตอร์ที่ไม่ใช่ประเภท แน่นอนคุณสามารถใช้floatและchar *ประเภทเป็นอาร์กิวเมนต์ปกติได้

บางทีผู้เขียนอาจใช้คอมไพเลอร์ที่ไม่เป็นไปตามมาตรฐานปัจจุบัน?


8
โปรดระบุลิงก์หรือสำเนาส่วนที่เกี่ยวข้องจากมาตรฐาน
thecoshman

2
@thecoshman ส่วนที่เกี่ยวข้องของมาตรฐาน + ข้อมูลเพิ่มเติมมีอยู่ในคำตอบ (โพสต์ใหม่) ของฉัน
Filip Roséen - อ้างอิง

1
ใน C ++ 11 เป็นไปได้ที่จะใช้ลิเทอรัลสตริงอักขระเป็นพารามิเตอร์ที่ไม่ใช่ประเภทเทมเพลต หากเทมเพลตของคุณใช้แพ็คtemplate<char ...cs>อักขระสตริงลิเทอรัลสามารถแปลงเป็นแพ็คดังกล่าวได้ในเวลาคอมไพล์ นี่คือการสาธิต ideone (การสาธิตคือ C ++ 14 แต่ง่ายต่อการเปลี่ยนกลับเป็น C ++ 11 - std::integer_sequenceเป็นความยากเพียงอย่างเดียว)
Aaron McDaid

โปรดทราบว่าคุณสามารถใช้char &*เป็นพารามิเตอร์เทมเพลตได้หากคุณกำหนดลิเทอรัลไว้ที่อื่น ใช้งานได้ดีเป็นวิธีแก้ปัญหาชั่วคราว
StenSoft

137

คำตอบง่ายๆ

มาตรฐานไม่อนุญาตให้จุดลอยตัวเป็นอาร์กิวเมนต์แม่แบบที่ไม่ใช่ประเภทซึ่งสามารถอ่านได้ในส่วนต่อไปนี้ของมาตรฐาน C ++ 11

14.3.2 / 1 อาร์กิวเมนต์ที่ไม่ใช่ประเภทของเทมเพลต [temp.arg.nontype]

เทมเพลตอาร์กิวเมนต์สำหรับพารามิเตอร์ที่ไม่ใช่ประเภทที่ไม่ใช่เทมเพลตจะต้องเป็นหนึ่งใน:

  • สำหรับเทมเพลตพารามิเตอร์ที่ไม่ใช่ประเภทของชนิดอินทิกรัลหรือการแจงนับนิพจน์ค่าคงที่ที่แปลงแล้ว (5.19) ของประเภทของพารามิเตอร์เทมเพลต

  • ชื่อของพารามิเตอร์เทมเพลตที่ไม่ใช่ประเภท หรือ

  • นิพจน์คงที่ (5.19) ที่กำหนดแอดเดรสของอ็อบเจ็กต์ที่มีระยะเวลาการจัดเก็บแบบคงที่และการเชื่อมโยงภายนอกหรือภายในหรือฟังก์ชันที่มีการเชื่อมโยงภายนอกหรือภายในรวมถึงเทมเพลตฟังก์ชันและรหัสเทมเพลตฟังก์ชัน แต่ไม่รวมสมาชิกคลาสที่ไม่คงที่แสดง (ละเว้น วงเล็บ) เป็น & id-expression ยกเว้นว่า & อาจถูกละไว้หากชื่ออ้างถึงฟังก์ชันหรืออาร์เรย์และจะถูกละเว้นหากพารามิเตอร์ template ที่เกี่ยวข้องเป็นการอ้างอิง หรือ

  • นิพจน์คงที่ประเมินเป็นค่าตัวชี้ค่าว่าง (4.10); หรือ

  • นิพจน์คงที่ประเมินเป็นค่าตัวชี้สมาชิกว่าง (4.11); หรือ

  • ตัวชี้ไปยังสมาชิกที่แสดงตามที่อธิบายไว้ใน 5.3.1


แต่ .. แต่ .. ทำไม!?

อาจเป็นเพราะการคำนวณจุดลอยตัวไม่สามารถแสดงในลักษณะที่แน่นอนได้ หากได้รับอนุญาตอาจ / จะส่งผลให้เกิดพฤติกรรมที่ผิดพลาด / แปลก ๆ เมื่อทำบางสิ่งเช่นนี้

func<1/3.f> (); 
func<2/6.f> ();

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


ฉันจะแสดงค่าทศนิยมเป็นอาร์กิวเมนต์ของเทมเพลตได้อย่างไร

ด้วยC++11คุณสามารถเขียนนิพจน์คงที่ขั้นสูง( constexpr ) ที่จะคำนวณตัวเศษ / ตัวส่วนของเวลาคอมไพล์ค่าลอยแล้วส่งทั้งสองนี้เป็นอาร์กิวเมนต์จำนวนเต็มแยกกัน

อย่าลืมกำหนดเกณฑ์บางประเภทเพื่อให้ค่าทศนิยมที่อยู่ใกล้กันให้ค่าตัวเศษ / ตัวหารเดียวกันมิฉะนั้นจะไม่มีจุดหมายเนื่องจากจะให้ผลลัพธ์เดียวกันกับที่กล่าวไว้ก่อนหน้านี้เพื่อเป็นเหตุผลที่ไม่อนุญาตให้ค่าทศนิยมเป็น non-type ข้อโต้แย้งแม่แบบ


56
โซลูชัน C ++ 11 <ratio>อธิบายโดย§20.10ว่าเป็น "เลขคณิตเชิงเหตุผลแบบคอมไพล์ - ไทม์" ซึ่งตัดตรงตัวอย่างของคุณ
Potatoswatter

1
@ Potatoswatter afaik ไม่มีวิธีใดใน STL ที่จะแปลงการลอยเป็นตัวเศษ / ตัวส่วนโดยใช้<ratio>?
Filip Roséen - อ้างอิง

3
นี่ไม่ได้ให้คำอธิบายที่น่าเชื่อถือจริงๆ จุดรวมของจุดลอยตัวคือมันแสดงถึงค่าที่แน่นอน คุณมีอิสระที่จะปฏิบัติกับตัวเลขที่คุณมีโดยประมาณกับอย่างอื่นและมักจะมีประโยชน์ที่จะทำเช่นนั้น แต่ตัวเลขนั้นแน่นอน
tmyklebu

4
@ FilipRoséen-refp: ตัวเลขทศนิยมทั้งหมดเป็นตัวเลขที่แน่นอน เลขคณิตทศนิยมถูกกำหนดไว้อย่างดีสำหรับทุกเป้าหมายที่ฉันรู้จัก การดำเนินการจุดลอยตัวส่วนใหญ่ให้ผลลัพธ์จุดลอยตัว ฉันรู้สึกซาบซึ้งที่คณะกรรมการไม่ต้องการบังคับให้ผู้ดำเนินการคอมไพเลอร์ใช้เลขคณิตทศนิยมที่อาจแปลกประหลาดของเป้าหมาย แต่ฉันไม่เชื่อว่า "เลขคณิตแตกต่างจากเลขคณิตจำนวนเต็ม" เป็นเหตุผลที่ดีในการห้ามใช้อาร์กิวเมนต์แม่แบบทศนิยม มันเป็นข้อ จำกัด โดยพลการในตอนท้ายของวัน
tmyklebu

5
@iheanyi: มาตรฐานบอกว่า12345 * 12345คืออะไร? (มันจะช่วยให้intแม่แบบพารามิเตอร์แม้ว่ามันจะไม่ได้ระบุความกว้างของ int ลงนามหรือไม่ว่าการแสดงออกที่เป็น UB ได้.)
tmyklebu

34

เพียงเพื่อให้เหตุผลประการหนึ่งว่าทำไมจึงเป็นข้อ จำกัด (อย่างน้อยที่สุดในมาตรฐานปัจจุบัน)

เมื่อจับคู่ความเชี่ยวชาญเทมเพลตคอมไพลเลอร์จะจับคู่อาร์กิวเมนต์ของเทมเพลตรวมถึงอาร์กิวเมนต์ที่ไม่ใช่ประเภท

ตามธรรมชาติแล้วค่าทศนิยมไม่แน่นอนและการนำไปใช้ไม่ได้ระบุไว้โดยมาตรฐาน C ++ ด้วยเหตุนี้จึงเป็นการยากที่จะตัดสินใจว่าเมื่ออาร์กิวเมนต์ที่ไม่ใช่ประเภททศนิยมสองตำแหน่งตรงกันจริงๆ:

template <float f> void foo () ;

void bar () {
    foo< (1.0/3.0) > ();
    foo< (7.0/21.0) > ();
}

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


16
นี่เกือบจะเป็นข้อโต้แย้งในการห้ามลอยจากภาษาทั้งหมด หรืออย่างน้อยที่สุดก็ห้ามตัว==ดำเนินการ :-) เรายอมรับความไม่ถูกต้องนี้แล้วที่รันไทม์ทำไมไม่รวบรวมเวลาด้วย
Aaron McDaid

3
เห็นด้วยกับ @AaronMcDaid นี่ไม่ใช่ข้อโต้แย้งมากนัก ดังนั้นคุณต้องระมัดระวังในคำจำกัดความ แล้วไงล่ะ? ตราบใดที่มันใช้งานได้กับสิ่งที่คุณได้รับจากค่าคงที่มันก็ค่อนข้างดีขึ้นแล้ว
einpoklum

1
ตอนนี้ C ++ 20 อนุญาตให้ float (ประเภทออบเจ็กต์อื่น) เป็นพารามิเตอร์เทมเพลตที่ไม่ใช่ประเภท ยังคง C ++ 20 ไม่ได้ระบุการใช้งานลอย นี่แสดงให้เห็นว่า einpoklum และ Aaron มีประเด็น
Andreas H.

20

แน่นอนคุณไม่สามารถใช้ตัวอักษรลอยเป็นพารามิเตอร์เทมเพลตได้ ดูส่วน 14.1 ( "ไม่ใช่ประเภทแม่แบบพารามิเตอร์จะมีหนึ่งดังต่อไปนี้ (เลือกพันธุ์ที่ผ่านการคัดเลือก) ประเภท ...")ของมาตรฐาน

คุณสามารถใช้การอ้างอิงถึง float เป็นพารามิเตอร์เทมเพลต:

template <class T, T const &defaultValue>
class GenericClass

.
.

float const c_four_point_six = 4.6; // at global scope

.
.

GenericClass < float, c_four_point_six> gcFlaot;

11
คุณสามารถ. แต่มันไม่ได้ทำในสิ่งเดียวกัน คุณไม่สามารถใช้การอ้างอิงเป็นค่าคงที่เวลาคอมไพล์

12

ตัดพารามิเตอร์ในคลาสของตนเองเป็น constexprs สิ่งนี้มีประสิทธิภาพคล้ายกับลักษณะที่กำหนดพารามิเตอร์ของคลาสด้วยชุดลอย

class MyParameters{
    public:
        static constexpr float Kd =1.0f;
        static constexpr float Ki =1.0f;
        static constexpr float Kp =1.0f;
};

จากนั้นสร้างเทมเพลตโดยใช้ประเภทคลาสเป็นพารามิเตอร์

  template <typename NUM, typename TUNING_PARAMS >
  class PidController {

      // define short hand constants for the PID tuning parameters
      static constexpr NUM Kp = TUNING_PARAMS::Kp;
      static constexpr NUM Ki = TUNING_PARAMS::Ki;
      static constexpr NUM Kd = TUNING_PARAMS::Kd;

      .... code to actually do something ...
};

แล้วใช้แบบนั้น ...

int main (){
    PidController<float, MyParameters> controller;
    ...
    ...
}

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


5

หากคุณตกลงที่จะมีค่าเริ่มต้นคงที่ต่อประเภทคุณสามารถสร้างประเภทเพื่อกำหนดเป็นค่าคงที่และเชี่ยวชาญได้ตามต้องการ

template <typename T> struct MyTypeDefault { static const T value; };
template <typename T> const T MyTypeDefault<T>::value = T();
template <> struct MyTypeDefault<double> { static const double value; };
const double MyTypeDefault<double>::value = 1.0;

template <typename T>
class MyType {
  public:
    MyType() { value = MyTypeDefault<T>::value; }
  private:
    T value;
 };

หากคุณมี C ++ 11 คุณสามารถใช้ constexpr เมื่อกำหนดค่าเริ่มต้น ด้วย C ++ 14 MyTypeDefault สามารถเป็นตัวแปรเทมเพลตซึ่งเป็นบิตที่สะอาดกว่าในเชิงไวยากรณ์

//C++14
template <typename T> constexpr T MyTypeDefault = T();
template <> constexpr double MyTypeDefault<double> = 1.0;

template <typename T>
class MyType {
  private:
    T value = MyTypeDefault<T>;
 };

2

คำตอบอื่น ๆ ให้เหตุผลที่ดีว่าทำไมคุณอาจไม่ต้องการพารามิเตอร์แม่แบบจุดลอยตัว แต่ข้อตกลงที่แท้จริงของ IMO คือความเท่าเทียมกันโดยใช้ '==' และความเท่าเทียมกันในระดับบิตไม่เหมือนกัน:

  1. -0.0 == 0.0แต่0.0และ-0.0ไม่เท่ากันในระดับบิต

  2. NAN != NAN

ความเท่าเทียมกันทั้งสองประเภทไม่ได้เป็นการยกเลิกที่ดีสำหรับความเท่าเทียมกันของประเภท: แน่นอนว่าจุดที่ 2 ทำให้การใช้==การกำหนดความเท่าเทียมกันไม่ถูกต้อง เราสามารถใช้ความเท่าเทียมกันในระดับบิตแทนได้ แต่ก็x != yไม่ได้หมายความถึงสิ่งนั้นMyClass<x>และMyClass<y>เป็นประเภทที่แตกต่างกัน (คูณ 2) ซึ่งค่อนข้างแปลก


1

คุณสามารถปลอมได้เสมอ ...

#include <iostream>

template <int NUM, int DEN>
struct Float
{
    static constexpr float value() { return (float)NUM / (float)DEN; }
    static constexpr float VALUE = value();
};

template <class GRAD, class CONST>
struct LinearFunc
{
    static float func(float x) { return GRAD::VALUE*x + CONST::VALUE; }
};


int main()
{
    // Y = 0.333 x + 0.2
    // x=2, y=0.866
    std::cout << " func(2) = "
              << LinearFunc<Float<1,3>, Float<1,5> > ::func(2) << std::endl;
}

อ้างอิง: http://code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html


3
A float! = จำนวนตรรกยะ ทั้งสองเป็นความคิดที่แยกจากกัน ค่าหนึ่งถูกคำนวณโดยใช้แมนทิสซาและเลขชี้กำลังส่วนอีกค่าหนึ่งคือมีเหตุผล - ไม่ใช่ทุกค่าที่แทนค่าได้ด้วยเหตุผลจะแทนค่าได้ด้วยกfloat.
Richard J.Ross III

2
@ RichardJ.RossIII A floatเป็นจำนวนที่มีเหตุผลมาก แต่มีfloats ที่ไม่สามารถแทนค่าเป็นอัตราส่วนของสองints ได้ แมนทิสซาเป็นจำนวนเต็มและเลขชี้กำลัง 2 ^ เป็นจำนวนเต็ม
Caleth

1

หากคุณไม่ต้องการให้ค่าคู่เป็นค่าคงที่เวลาคอมไพล์คุณสามารถส่งผ่านเป็นตัวชี้ได้:

#include <iostream>

extern const double kMyDouble = 0.1;;

template <const double* MyDouble>
void writeDouble() {
   std::cout << *MyDouble << std::endl; 
}

int main()
{
    writeDouble<&kMyDouble>();
   return 0;
}

ข้อมูลอ้างอิงน่าจะดีกว่าดูคำตอบของ
@moonshadow

1
สิ่งนี้ช่วยลดเวลาคอมไพล์ได้จริงหรือไม่?
Ant6n

1

เริ่มต้นด้วย C ++ 20 นี้เป็นไปได้

สิ่งนี้ยังให้คำตอบสำหรับคำถามเดิม:

Why can't I use float value as a template parameter?

เนื่องจากยังไม่มีใครนำมาใช้ในมาตรฐาน ไม่มีเหตุผลพื้นฐาน

ในพารามิเตอร์เทมเพลตที่ไม่ใช่ประเภท C ++ 20 สามารถลอยและแม้แต่คลาสออบเจ็กต์ได้แล้ว

มีข้อกำหนดบางอย่างเกี่ยวกับคลาสอ็อบเจ็กต์ (ต้องเป็นประเภทตามตัวอักษร ) และปฏิบัติตามข้อกำหนดอื่น ๆ เพื่อยกเว้นกรณีทางพยาธิวิทยาเช่นตัวดำเนินการที่กำหนดโดยผู้ใช้ == ( รายละเอียด )

เรายังสามารถใช้ auto

template <auto Val>
struct Test {
};

struct A {};
static A aval;
Test<aval>  ta;
Test<A{}>  ta2;
Test<1.234>  tf;
Test<1U>  ti;

โปรดทราบว่า GCC 9 (และ 10) การดำเนินการระดับที่ไม่ใช่ประเภทพารามิเตอร์แม่แต่ไม่ลอยเลย


0

หากคุณต้องการแสดงเฉพาะความแม่นยำคงที่คุณสามารถใช้เทคนิคเช่นนี้เพื่อแปลงพารามิเตอร์ float เป็น int

ตัวอย่างเช่นอาร์เรย์ที่มีปัจจัยการเติบโต 1.75 สามารถสร้างได้ดังนี้สมมติว่ามีความแม่นยำ 2 หลัก (หารด้วย 100)

template <typename _Kind_, int _Factor_=175>
class Array
{
public:
    static const float Factor;
    _Kind_ * Data;
    int Size;

    // ...

    void Resize()
    {
         _Kind_ * data = new _Kind_[(Size*Factor)+1];

         // ...
    }
}

template<typename _Kind_, int _Factor_>
const float Array<_kind_,_Factor_>::Factor = _Factor_/100;

หากคุณไม่ชอบการแทนค่า 1.75 เป็น 175 ในรายการอาร์กิวเมนต์เทมเพลตคุณสามารถรวมไว้ในมาโครได้เสมอ

#define FloatToIntPrecision(f,p) (f*(10^p))

template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)>
// ...

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