Подтвердить что ты не робот

Понимание нестандартных параметров шаблона

У меня проблема с пониманием следующего абзаца Per С++ 11 Standard N3485 Раздел 14.1.7. Я думаю, что более важно понять обоснование, а не запоминать факты.

Параметр шаблона, не относящийся к типу, не должен быть объявлен как имеющий тип с плавающей точкой, классом или void.
  [Пример:

template<double d> class X; // error
template<double* pd> class Y; // OK
template<double& rd> class Z; // OK

-end пример]

У меня есть некоторые вопросы относительно этого правила:

  • Есть ли причина, почему тип floating point не может использоваться в качестве параметра шаблона? В чем смысл этого? Я знаю, что это верно до С++ 11, и это также верно для стандарта С++ 11.

  • Зачем использовать pointer или reference для типов с плавающей запятой в качестве параметров без шаблона, но не для сырых типов с плавающей запятой? В чем здесь большая разница?

Благодарим вас за помощь.

4b9b3361

Ответ 1

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

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

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

Другая аналогичная проблема заключается в определении того, являются ли два экземпляра одних и тех же шаблонов тем же типом:

template<double D>
struct X
{
    // ...
};

int main()
{
    X<3.0> x;
    X<some_constant_expression()> y;
}

Являются ли экземпляры x и y одного и того же класса? Чтобы решить это, необходимо выполнить проверку равенства между 3.0 и some_constant_expression().

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

В отношении вышеприведенного сценария ответ на указатели прост: указатели являются целыми значениями, и сравнение между указателями хорошо определено.

Что касается ссылок, доказательства показывают, что они фактически обрабатываются как указатели:

#include <type_traits>

double a = 0.0;
double b = 0.0;

template<double& D>
struct X : std::false_type { };

template<>
struct X<a> : std::true_type { }

int main()
{
    static_assert(X<a>::value, "!"); // Does not fire
    static_assert(X<b>::value, "!"); // Fires
}

Кроме того, в параграфе 14.4/1 стандарта С++ 11:

Два идентификатора шаблона относятся к одному и тому же классу или функции, если

- [...]

- их соответствующие аргументы шаблона типа non-type для целочисленного или перечисляемого типа имеют одинаковые значения и

- [...]

- соответствующие им не-тип шаблона-аргументы ссылочного типа относятся к одному и тому же внешнему объекту или функции и

- [...]

[Пример:

template<class E, int size> class buffer { / ... / };
buffer<char,2*512> x;
buffer<char,1024> y;

объявляет x и y одинаковым типом, а [...]

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

Ответ 2

Чтобы понять это, рассмотрим, что интегральные типы и указатели всегда имеют взаимно однозначное отношение со своим буквальным представлением.

Но тогда рассмотрим шаблон не-типа Foo<float>.

Предположим, что он специализирован для не двоично-представимого числа, такого как 0.1: Foo<0.1>, и пусть компилятор украшает имя символа на основе специализации, и вы завершаете что-то вроде Foo_3DCCCCCC, потому что компилятор используя "round down" для 32-разрядной реплики IEEE 754.

Но тогда скажем, что пользователь этого кода компилируется таким образом, что компилятор вместо этого выбирает "round to positive infinity". Затем вместо этого имя специализации Foo_3DCCCCCD, которое является совершенно другой функцией, отличной от той, которая ранее была специализирована в другой единицы перевода.

Если вы не начинаете составлять множество правил для обработки всех этих вещей (что касается NaN, бесконечности и других не-нормальных чисел?), то вы открываете себе несоответствия и всевозможные возможные проблемы.