Почему я не могу использовать значение с плавающей запятой в качестве параметра шаблона?

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'

Я читаю «Структуры данных для программистов игр» Рона Пентона, автор пропускает float, но когда я пробую, кажется, что он не компилируется.

yokks
источник
1
Имеет ли автор действительно использовать в floatкачестве параметра шаблона не типа ? В какой главе это?
K-балл
1
Нашел, он находится в разделе «Использование значений в качестве параметров шаблона» ...
К-балл

Ответы:

37

Текущий стандарт C ++ не позволяет floatиспользовать литералы (т.е. действительные числа) или символьные строки в качестве параметров, не относящихся к типу . Вы можете, конечно , использовать floatи char *тип в качестве обычных аргументов.

Возможно, автор использует компилятор, который не соответствует текущему стандарту?

Филип Розеен - ссылка
источник
8
Пожалуйста, дайте ссылку или копию соответствующего раздела из стандарта
thecoshman
2
@thecoshman соответствующий раздел стандарта + дополнительная информация доступна в моем (недавно опубликованном) ответе.
Филип Розеен - ссылка
1
В C ++ 11 практически возможно использовать литерал символьной строки в качестве параметра шаблона, не являющегося типом. Если ваш шаблон принимает пакет символов template<char ...cs>, то строковый литерал может быть преобразован в такой пакет во время компиляции. Вот демо на идеоне . (Демо - C ++ 14, но его легко перенести обратно на C ++ 11 - std::integer_sequenceэто единственная трудность)
Аарон МакДэйд,
Обратите внимание, что вы можете использовать char &*в качестве параметра шаблона, если определите литерал где-то еще. Очень хорошо работает как обходной путь.
StenSoft
137

ПРОСТОЙ ОТВЕТ

Стандарт не допускает использование плавающих точек в качестве аргументов шаблона, не являющихся типом , о чем можно прочитать в следующем разделе стандарта C ++ 11;

14.3.2 / 1 Шаблон, не являющийся аргументом типа [temp.arg.nontype]

Аргумент шаблона для параметра шаблона, не являющегося типом и шаблоном, должен быть одним из следующих:

  • для не типового параметра-шаблона целочисленного или перечислимого типа - преобразованное постоянное выражение (5.19) типа параметра-шаблона;

  • имя нетипового параметра-шаблона; или

  • постоянное выражение (5.19), которое обозначает адрес объекта со статической продолжительностью хранения и внешней или внутренней связью или функцию с внешней или внутренней связью, включая шаблоны функций и идентификаторы шаблонов функций, но исключая нестатические члены класса, выраженное (игнорирование круглые скобки) как & id-expression, за исключением того, что & может быть опущено, если имя относится к функции или массиву, и должно быть опущено, если соответствующий параметр-шаблон является ссылкой; или

  • постоянное выражение, результатом которого является значение нулевого указателя (4.10); или

  • постоянное выражение, которое оценивается как значение указателя на нулевой член (4.11); или

  • указатель на член, выраженный, как описано в 5.3.1.


Но .. но .. ПОЧЕМУ !?

Вероятно, это связано с тем, что вычисления с плавающей запятой не могут быть представлены точно. Если бы это было разрешено, это могло бы / привело бы к ошибочному / странному поведению при выполнении таких действий;

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

Мы хотели вызвать одну и ту же функцию дважды, но это может быть не так, поскольку представление двух вычислений с плавающей запятой не обязательно будет точно таким же.


Как мне представить значения с плавающей запятой в качестве аргументов шаблона?

С C++11его помощью вы можете написать несколько довольно сложных константных выражений ( constexpr ), которые будут вычислять числитель / знаменатель времени компиляции плавающего значения, а затем передавать эти два как отдельные целочисленные аргументы.

Не забудьте определить какой-то порог, чтобы значения с плавающей запятой, близкие друг к другу, давали один и тот же числитель / знаменатель , в противном случае это бессмысленно, поскольку тогда будет получен тот же результат, который ранее упоминался как причина не разрешать значения с плавающей запятой как не типовые аргументы шаблона .

Филип Розеен - ссылка
источник
56
Решение C ++ 11 <ratio>описывается в §20.10 как «Рациональная арифметика времени компиляции». Что прямо к вашему примеру.
Potatoswatter
1
@Potatoswatter afaik нет никакого метода в STL для преобразования числа с плавающей запятой в числитель / знаменатель с помощью <ratio>?
Филип Розеен - ссылка
3
Это не совсем убедительное объяснение. Вся суть плавающей точки в том, что она точно представляет значения. Вы можете рассматривать имеющиеся числа как приближения к чему-то еще, и это часто бывает полезно, но сами числа точны.
tmyklebu
4
@ FilipRoséen-refp: Все числа с плавающей запятой точны. Арифметика с плавающей запятой четко определена для каждой известной мне цели. Большинство операций с плавающей запятой дают результаты с плавающей запятой. Я понимаю, что комитет не желает заставлять разработчиков компилятора реализовывать, возможно, причудливую арифметику с плавающей запятой, но я не верю, что «арифметика отличается от целочисленной арифметики» является хорошей причиной запретить аргументы шаблона с плавающей запятой. В конце концов, это произвольное ограничение.
tmyklebu
5
@iheanyi: Стандарт говорит, что 12345 * 12345такое? (Это действительно позволяют intпараметры шаблона , даже если он не определяет ширину подписанных межд или же это выражение UB.)
tmyklebu
34

Просто чтобы указать одну из причин, почему это ограничение (по крайней мере, в текущем стандарте).

При сопоставлении специализаций шаблона компилятор сопоставляет аргументы шаблона, включая аргументы, не относящиеся к типу.

По своей природе значения с плавающей запятой неточны, и их реализация не определяется стандартом C ++. В результате трудно решить, действительно ли совпадают два аргумента не типа с плавающей запятой:

template <float f> void foo () ;

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

Эти выражения не обязательно образуют один и тот же «битовый шаблон», и поэтому невозможно гарантировать, что они используют одну и ту же специализацию - без специальной формулировки, чтобы охватить это.

Ричард Корден
источник
16
Это почти аргумент в пользу полного запрета плавающих объектов в языке. Или, как минимум, забанить ==оператора :-) Мы уже принимаем эту неточность во время выполнения, почему бы и во время компиляции?
Аарон МакДэйд,
3
Согласитесь с @AaronMcDaid, это не большой аргумент. Так что нужно быть осторожным в определении. Ну и что? Пока это работает для вещей, которые вы получаете от констант, это уже значительное улучшение.
einpoklum
1
C ++ 20 теперь позволяет использовать float (другие типы объектов) как параметры шаблона, не являющиеся типами. Тем не менее C ++ 20 не определяет реализацию с плавающей запятой. Это показывает, что Эйнпоклум и Аарон правы.
Андреас Х.
20

Действительно, вы не можете использовать литералы с плавающей запятой в качестве параметров шаблона. См. Раздел 14.1 («Параметр шаблона без типа должен иметь один из следующих (необязательно cv-квалифицированных) типов ...») стандарта.

Вы можете использовать ссылку на 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! = Рациональное число. Это две очень разные идеи. Одно вычисляется с помощью мантиссы и показателя степени, а другое является рациональным - не каждое значение, представимое рациональным числом, может быть представлено с помощью a float.
Ричард Дж. Росс III
2
@ RichardJ.RossIII A float- определенно рациональное число, но есть floats, которые не могут быть представлены как отношения двух ints. Мантисса представляет собой целое число, а показатель степени 2 ^ - целое число
Калет
1

Если вам не нужно, чтобы значение double было константой времени компиляции, вы можете передать его как указатель:

#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;
}
user3233025
источник
Ссылка, вероятно, лучше, см. Ответ
@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) реализует параметры шаблона, не относящиеся к типу, но еще не для float .

Андреас Х.
источник
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)>
// ...
jurujen
источник
так и должно быть, ...::Factor = _Factor_/100.0;иначе это будет целочисленное деление.
alfC 06