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>);
    ...
}
Другие вопросы по тегам