C++14 Шаблоны переменных: какова их цель? Любой пример использования?
C++14 позволит создавать переменные, которые являются шаблонами. Обычным примером является переменная 'pi', которую можно прочитать, чтобы получить значение математической константы π для различных типов (3 для int
; ближайшее значение возможно с float
, так далее.)
Кроме того, что мы можем получить эту функцию, просто обернув переменную в шаблонную структуру или класс, как это сочетается с преобразованиями типов? Я вижу некоторые совпадения.
И кроме примера с пи, как это будет работать с неконстантными переменными? Любой пример использования, чтобы понять, как максимально использовать такую функцию и какова ее цель?
5 ответов
И кроме примера с пи, как это будет работать с неконстантными переменными?
В настоящее время создается впечатление, что переменные создаются отдельно для типа. то есть, вы можете назначить 10 для n<int>
и это будет отличаться от определения шаблона.
template<typename T>
T n = T(5);
int main()
{
n<int> = 10;
std::cout << n<int> << " "; // 10
std::cout << n<double> << " "; // 5
}
Если декларация const
Это только для чтения. Если это constexpr
, как все constexpr
декларации, он не имеет большого применения снаружи constexpr
(ressions).
Кроме того, что мы можем получить эту функцию, просто обернув переменную в шаблонную структуру или класс, как это сочетается с преобразованиями типов?
Это должно быть простое предложение. Я не вижу, как это существенно влияет на преобразования типов. Как я уже говорил, тип переменной - это тип, с которым вы создали экземпляр шаблона. т.е. decltype(n<int>)
это инт. decltype((double)n<int>)
двойной и так далее.
Любой пример использования, чтобы понять, как максимально использовать такую функцию и какова ее цель?
N3651 дает краткое обоснование.
Увы, существующие правила C++ не позволяют объявлению шаблона объявлять переменную. Есть хорошо известные обходные пути для этой проблемы:
• использовать статические данные constexpr членов шаблонов классов
• использовать шаблоны функций constexpr, возвращающие нужные значения
Эти обходные пути известны десятилетиями и хорошо документированы. Стандартные классы, такие как std::numeric_limits, являются типичными примерами. Хотя эти обходные пути не идеальны, их недостатки в некоторой степени терпимы, поскольку в эпоху C++03 только простые встроенные константы типов пользовались беспрепятственной прямой и эффективной поддержкой во время компиляции. Все это изменилось с принятием переменных constexpr в C++11, что расширило прямую и эффективную поддержку констант пользовательских типов. Теперь программисты делают константы (типов классов) все более очевидными в программах. Так что растут путаницы и разочарования, связанные с обходными путями.
...
Основные проблемы со "статическим членом данных":
• они требуют "дублирующих" объявлений: один раз внутри шаблона класса, один раз за пределами шаблона класса, чтобы обеспечить "реальное" определение в случае использования констант odr.
• программисты недовольны и смущены необходимостью предоставления дважды одинакового объявления. Напротив, "обычные" объявления констант не нуждаются в повторных объявлениях.
...
Хорошо известными примерами в этой категории являются, вероятно, статические функции-члены numeric_limits или такие функции, как
boost::constants::pi<T>()
и т. д. Шаблоны функций Constexpr не страдают от проблемы "двойных объявлений", которая есть у статических членов данных; кроме того, они обеспечивают функциональную абстракцию. Тем не менее, они вынуждают программиста заранее выбирать на месте определения способ доставки констант: либо по константной ссылке, либо по обычному неконферентному типу. Если доставлено по константной ссылке, то константы должны систематически размещаться в статическом хранилище; если не по ссылочному типу, то константы нужно копировать. Копирование не является проблемой для встроенных типов, но является демонстрацией для пользовательских типов с семантикой значений, которые не являются просто обертками вокруг крошечных встроенных типов (например, матрица, или целое число, или bigfloat и т.д.). " обычные переменные const (expr) не страдают от этой проблемы. Предоставляется простое определение, и решение о том, должны ли константы на самом деле располагаться в хранилище, зависит только от использования, а не от определения.
мы можем иметь эту функцию, просто оборачивая переменную в шаблонную структуру или класс
Да, но это будет бесполезная синтаксическая соль. Не здоров для кровяного давления.
pi<double>
передает намерение лучше, чем pi<double>::value
, Коротко и точно. Этого достаточно в моей книге, чтобы разрешить и поддержать этот синтаксис.
Другой практический пример шаблонов переменных C++14 - когда вам нужна функция для передачи чего-либо в std::accumulate
:
template<typename T>
T const & (*maxer) (T const &, T const &) = std::max<T>;
std::accumulate(some.begin(), some.end(), initial, maxer<float>);
Обратите внимание, что с помощью std::max<T>
недостаточно, потому что не может вывести точную подпись. В этом конкретном примере вы можете использовать max_element
вместо этого, но дело в том, что существует целый класс функций, которые разделяют это поведение.
Интересно, возможно ли что-то в этом направлении: (при условии наличия лямбда-шаблонов)
void some_func() {
template<typename T>
std::map<int, T> storage;
auto store = []<typename T>(int key, const T& value) { storage<T>[key] = value; };
store(0, 2);
store(1, "Hello"s);
store(2, 0.7);
// All three values are stored in a different map, according to their type.
}
Теперь это полезно?
Как более простое использование, обратите внимание, что инициализация pi<T>
использует явное преобразование (явный вызов унарного конструктора) и неравномерную инициализацию. Что означает, что, учитывая тип radians
с конструктором radians(double)
, ты можешь написать pi<radians>
,
Ну, вы можете использовать это, чтобы написать код времени компиляции следующим образом:
#include <iostream>
template <int N> const int ctSquare = N*N;
int main() {
std::cout << ctSquare<7> << std::endl;
}
Это значительное улучшение по сравнению с аналогом
#include <iostream>
template <int N> struct ctSquare {
static const int value = N*N;
};
int main() {
std::cout << ctSquare<7>::value << std::endl;
}
что люди писали для выполнения метапрограммирования шаблонов до введения переменных шаблонов. Для не типовых значений мы смогли сделать это, так как C++11 с constexpr
Таким образом, переменные шаблона имеют только то преимущество, что допускают вычисления на основе типов для шаблонов переменных.
TL; DR: они не позволяют нам делать то, что мы не могли делать раньше, но они делают шаблонное метапрограммирование менее PITA.
У меня здесь есть пример использования.
template<typename CT> constexpr CT MARK = '%';
template<> constexpr wchar_t MARK<wchar_t> = L'%';
которые используются в шаблоне обработки строк.
template <typename CT>
void ProcessString(const std::basic_string<CT>& str)
{
auto&& markpos = str.find(MARK<CT>);
...
}