Tại sao tôi không thể sử dụng giá trị float làm tham số mẫu?


120

Khi tôi cố gắng sử dụng floatlàm tham số mẫu, trình biên dịch sẽ kêu mã này, trong khi inthoạt động tốt.

Có phải vì tôi không thể sử dụng floatlàm tham số mẫu không?

#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;       
}

Lỗi:

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'

Tôi đang đọc "Cấu trúc dữ liệu cho lập trình viên trò chơi" của Ron Penton, tác giả vượt qua a float, nhưng khi tôi thử thì nó dường như không biên dịch.


1
Tác giả có thực sự sử dụng floatlàm tham số mẫu không phải kiểu không? Chương nào vậy?
K-ball

1
Đã tìm thấy nó, nó nằm ở "Sử dụng các giá trị làm thông số mẫu" ...
K-ball

Câu trả lời:


37

Tiêu chuẩn C ++ hiện tại không cho phép float(tức là số thực) hoặc các ký tự chuỗi ký tự được sử dụng làm tham số không phải kiểu mẫu . Tất nhiên, bạn có thể sử dụng các floatchar *loại làm đối số bình thường.

Có lẽ tác giả đang sử dụng một trình biên dịch không theo tiêu chuẩn hiện hành?


8
Vui lòng cung cấp một liên kết đến hoặc sao chép các phần có liên quan từ các tiêu chuẩn
thecoshman

2
@thecoshman, phần liên quan của tiêu chuẩn + thêm thông tin có sẵn trong câu trả lời (mới được đăng) của tôi.
Filip Roséen - refp 17/12/12

1
Trong C ++ 11, có thể sử dụng một chuỗi ký tự là một tham số không phải kiểu mẫu. Nếu mẫu của bạn có một gói ký tự template<char ...cs>, thì chuỗi ký tự có thể được chuyển đổi thành một gói như vậy tại thời điểm biên dịch. Đây là một bản demo trên Ideone . (Demo là C ++ 14, nhưng nó dễ dàng đến cổng nó trở lại C ++ 11 - std::integer_sequencelà khó khăn duy nhất)
Aaron McDaid

Lưu ý rằng bạn có thể sử dụng char &*làm tham số mẫu nếu bạn xác định chữ ở một nơi khác. Hoạt động khá tốt như một cách giải quyết.
StenSoft,

137

CÂU TRẢ LỜI ĐƠN GIẢN

Tiêu chuẩn không cho phép các dấu chấm động dưới dạng đối số mẫu không phải kiểu , có thể được đọc trong phần sau của tiêu chuẩn C ++ 11;

14.3.2 / 1 Đối số không phải kiểu mẫu [temp.arg.nontype]

Đối số mẫu cho một tham số mẫu không phải kiểu, không phải mẫu phải là một trong:

  • đối với tham số mẫu không phải kiểu của kiểu tích phân hoặc kiểu liệt kê, một biểu thức hằng số được chuyển đổi (5.19) của kiểu tham số khuôn mẫu;

  • tên của một tham số mẫu không phải kiểu; hoặc là

  • một biểu thức hằng số (5.19) chỉ định địa chỉ của một đối tượng có thời lượng lưu trữ tĩnh và liên kết bên ngoài hoặc bên trong hoặc một hàm có liên kết bên ngoài hoặc bên trong, bao gồm các mẫu hàm và id mẫu hàm nhưng loại trừ các thành viên lớp không tĩnh, được biểu thị (bỏ qua dấu ngoặc đơn) dưới dạng & id-expression, ngoại trừ & có thể bị bỏ qua nếu tên tham chiếu đến một hàm hoặc mảng và sẽ bị bỏ qua nếu tham số mẫu tương ứng là một tham chiếu; hoặc là

  • một biểu thức hằng cho giá trị con trỏ null (4.10); hoặc là

  • một biểu thức hằng ước tính giá trị con trỏ thành viên null (4.11); hoặc là

  • một con trỏ tới thành viên được thể hiện như mô tả trong 5.3.1.


Nhưng .. nhưng .. TẠI SAO !?

Nó có thể là do thực tế là các phép tính dấu phẩy động không thể được biểu diễn một cách chính xác. Nếu nó được cho phép, nó có thể / sẽ dẫn đến hành vi sai lầm / kỳ lạ khi làm điều gì đó như thế này;

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

Chúng tôi muốn gọi cùng một hàm hai lần nhưng điều này có thể không đúng vì biểu diễn dấu phẩy động của hai phép tính không được đảm bảo là hoàn toàn giống nhau.


Làm cách nào để biểu diễn các giá trị dấu phẩy động dưới dạng đối số mẫu?

Với C++11bạn có thể viết một số biểu thức hằng số khá nâng cao ( constexpr ) sẽ tính toán tử số / mẫu số của thời gian biên dịch giá trị động và sau đó chuyển hai biểu thức này thành các đối số nguyên riêng biệt.

Hãy nhớ xác định một số loại ngưỡng để các giá trị dấu phẩy động gần nhau mang lại cùng một tử số / mẫu số , nếu không thì nó hơi vô nghĩa vì sau đó nó sẽ mang lại cùng một kết quả đã đề cập trước đó là lý do không cho phép các giá trị dấu phẩy động là không phải kiểu đối số mẫu .


56
Giải pháp C ++ 11 được <ratio>§20.10 mô tả là "Số học hữu tỉ theo thời gian biên dịch". Điều nào cắt đúng với ví dụ của bạn.
Potatoswatter

1
@Potatoswatter afaik không có bất kỳ phương pháp nào trong STL để chuyển đổi một float thành tử số / mẫu số bằng cách sử dụng <ratio>?
Filip Roséen - refp 17/12/12

3
Điều này không thực sự đưa ra một lời giải thích thuyết phục. Toàn bộ điểm của dấu phẩy động là nó đại diện cho các giá trị một cách chính xác. Bạn có thể tự do coi những con số mình có là những con số gần đúng với một thứ gì đó khác và điều đó thường hữu ích khi làm như vậy, nhưng bản thân những con số là chính xác.
tmyklebu

4
@ FilipRoséen-refp: Tất cả các số dấu phẩy động đều chính xác. Số học dấu phẩy động được xác định rõ ràng trên mọi mục tiêu mà tôi biết. Hầu hết các phép toán dấu phẩy động tạo ra kết quả dấu phẩy động. Tôi có thể đánh giá cao việc ủy ​​ban không muốn buộc những người triển khai trình biên dịch thực hiện số học dấu phẩy động có thể kỳ lạ của mục tiêu, nhưng tôi không tin rằng "số học khác với số nguyên" là lý do chính đáng để cấm các đối số mẫu dấu phẩy động. Đó là một hạn chế tùy ý vào cuối ngày.
tmyklebu

5
@iheanyi: Tiêu chuẩn có nói 12345 * 12345là gì không? (Nó không cho phép intcác thông số mẫu mặc dù nó không xác định chiều rộng của một int ký kết hoặc xem biểu thức đó là UB.)
tmyklebu

34

Chỉ để cung cấp một trong những lý do tại sao đây là một hạn chế (ít nhất là trong tiêu chuẩn hiện tại).

Khi đối sánh các chuyên môn của mẫu, trình biên dịch sẽ khớp với các đối số của mẫu, bao gồm các đối số không phải kiểu.

Về bản chất, các giá trị dấu phẩy động không chính xác và việc triển khai chúng không được tiêu chuẩn C ++ chỉ định. Do đó, rất khó để quyết định khi nào hai đối số dấu phẩy động thực sự khớp với nhau:

template <float f> void foo () ;

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

Những biểu thức này không nhất thiết phải tạo ra cùng một "mẫu bit" và do đó sẽ không thể đảm bảo rằng chúng đã sử dụng cùng một chuyên ngành - nếu không có từ ngữ đặc biệt để đề cập đến điều này.


16
Đây gần như là một lập luận để cấm trôi nổi hoàn toàn từ ngôn ngữ. Hoặc, ở mức tối thiểu, cấm ==nhà điều hành :-) Chúng tôi đã chấp nhận sự không chính xác này trong thời gian chạy, tại sao lúc biên dịch lại không?
Aaron McDaid

3
Đồng ý với @AaronMcDaid, điều này không có gì đáng bàn cãi. Vì vậy, bạn cần phải cẩn thận trong định nghĩa. Vậy thì sao? Miễn là nó hoạt động với những thứ bạn nhận được từ hằng số, thì nó đã khá cải thiện.
einpoklum

1
C ++ 20 bây giờ cho phép float (một kiểu đối tượng khác) làm tham số mẫu không phải kiểu. Vẫn C ++ 20 không chỉ định thực hiện float. Điều này cho thấy einpoklum và Aaron có lý.
Andreas H.

20

Thật vậy, bạn không thể sử dụng các ký tự float làm tham số mẫu. Xem phần 14.1 ("Tham số mẫu không phải kiểu phải có một trong các kiểu (tùy chọn cv đủ điều kiện) sau đây ...") của tiêu chuẩn.

Bạn có thể sử dụng tham chiếu đến float làm tham số mẫu:

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
Bạn có thể. nhưng nó không làm điều tương tự. Bạn không thể sử dụng tham chiếu làm hằng số thời gian biên dịch.

12

Bao bọc (các) tham số trong lớp riêng của chúng dưới dạng trình kết hợp. Về thực tế, điều này tương tự như một đặc điểm vì nó tham số hóa lớp bằng một tập hợp các phao.

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

và sau đó tạo một mẫu lấy kiểu lớp làm tham số

  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 ...
};

và sau đó sử dụng nó như vậy ...

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

Điều này cho phép trình biên dịch đảm bảo rằng chỉ một phiên bản mã duy nhất được tạo cho mỗi phiên bản mẫu với cùng một gói tham số. Điều đó giải quyết tất cả các vấn đề và bạn có thể sử dụng float và nhân đôi như constexpr bên trong lớp mẫu.


5

Nếu bạn có thể có một mặc định cố định cho mỗi kiểu, bạn có thể tạo một kiểu để xác định nó như một hằng số và chuyên biệt hóa nó khi cần.

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;
 };

Nếu bạn có C ++ 11, bạn có thể sử dụng constexpr khi xác định giá trị mặc định. Với C ++ 14, MyTypeDefault có thể là một biến mẫu về mặt cú pháp rõ ràng hơn một chút.

//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

Các câu trả lời khác đưa ra lý do chính đáng tại sao bạn có thể không muốn các tham số mẫu dấu phẩy động, nhưng điểm khó khăn thực sự của IMO là bình đẳng sử dụng '==' và bình đẳng bit không giống nhau:

  1. -0.0 == 0.0, nhưng 0.0-0.0không bằng nhau

  2. NAN != NAN

Không phải loại bình đẳng nào cũng là một giải pháp tốt cho bình đẳng loại: Tất nhiên, điểm 2. làm cho việc sử dụng ==không hợp lệ để xác định bình đẳng loại. Thay vào đó, người ta có thể sử dụng bình đẳng bitwise, nhưng sau đó x != ykhông ngụ ý điều đó MyClass<x>MyClass<y>là các kiểu khác nhau (bằng 2), điều này sẽ khá lạ.


1

Bạn luôn có thể giả mạo nó ...

#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;
}

Tham khảo: http://code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html


3
A float! = Số hữu tỉ. Hai ý tưởng rất riêng biệt. Một được tính thông qua một định trị và một số mũ, cái kia, tốt, là một giá trị hợp lý - không phải mọi giá trị có thể biểu diễn bởi một số hữu tỷ đều có thể biểu diễn bằng a float.
Richard J. Ross III

2
@ RichardJ.RossIII A floatrất chắc chắn là một số hữu tỉ, nhưng có floatnhững số không thể biểu diễn được dưới dạng tỷ số của hai ints. Mantissa là một Integer, và 2 ^ mũ là một Integer
Caleth

1

Nếu bạn không cần double là hằng số thời gian biên dịch, bạn có thể chuyển nó vào dưới dạng một con trỏ:

#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;
}

Một tài liệu tham khảo có lẽ tốt hơn, xem @moonshadow 's câu trả lời
einpoklum

1
Điều này có thực sự giảm đúng cách tại thời điểm biên dịch không?
Ant6n,

1

Bắt đầu với C ++ 20 điều này là có thể .

Điều này cũng đưa ra câu trả lời cho câu hỏi ban đầu:

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

Bởi vì chưa ai thực hiện nó trong tiêu chuẩn. Không có lý do cơ bản.

Trong C ++, 20 tham số mẫu không phải kiểu bây giờ có thể là float và thậm chí là các đối tượng lớp.

Có một số yêu cầu đối với các đối tượng lớp (chúng phải là kiểu chữ ) và đáp ứng một số yêu cầu khác để loại trừ các trường hợp bệnh lý như toán tử do người dùng xác định == ( Chi tiết ).

Chúng tôi thậm chí có thể sử dụng auto

template <auto Val>
struct Test {
};

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

Lưu ý rằng GCC 9 (và 10) triển khai các tham số mẫu không phải kiểu của lớp, nhưng chưa áp dụng cho float .


0

Nếu bạn chỉ muốn biểu diễn một độ chính xác cố định, thì bạn có thể sử dụng một kỹ thuật như thế này để chuyển đổi một tham số float thành một int.

Ví dụ, một mảng có hệ số tăng trưởng là 1,75 có thể được tạo như sau giả sử có 2 chữ số chính xác (chia cho 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;

Nếu bạn không thích cách biểu diễn 1,75 là 175 trong danh sách đối số mẫu thì bạn luôn có thể bọc nó trong một số macro.

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

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

nó nên ...::Factor = _Factor_/100.0;nếu không nó sẽ là số nguyên chia.
alfC
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.