Шаблоны C++ - это всего лишь замаскированные макросы?

Я программировал на C++ в течение нескольких лет, и я довольно много использовал STL и несколько раз создавал свои собственные классы шаблонов, чтобы увидеть, как это делается.

Теперь я пытаюсь глубже интегрировать шаблоны в свой объектно-ориентированный дизайн, и мне постоянно возвращается ноющая мысль: на самом деле это всего лишь макросы ... Вы могли бы реализовать (скорее УЖЕ) auto_ptrs, используя #defines, если действительно хотеть.

Такой образ мыслей о шаблонах помогает мне понять, как на самом деле будет работать мой код, но я чувствую, что как-то упускаю суть. Макросы означают воплощение зла, но «метапрограммирование шаблонов» в моде.

Итак, каковы настоящие различия? и как шаблоны могут избежать опасностей, к которым вас ведет #define, например

  • Непостижимые ошибки компилятора в места, где вы их не ожидаете?
  • Раздутие кода?
  • Трудности с отслеживанием кода?
  • Установка точек останова отладчика?

Почему вы считаете, что «метапрограммирование шаблонов - это все в моде»?

Partial 09.09.2009 00:47

Нет, это не так. :-) И ваш вопрос очень загружен, потому что шаблоны не помогают ни с одной из упомянутых вами проблем. Вместо этого они помогают с совершенно другим классом проблем, которые вы легко игнорируете. Вопрос предполагает ответ, и если бы у меня была сила, я бы решил дисквалифицировать его как субъективный и аргументированный.

Omnifarious 14.12.2009 23:17

@Omnifarious - это был комментарий к этому вопросу или к тому, который был объединен с моим? Эта суета над мертвым вопросом за год ломает мне голову ...

Roddy 15.12.2009 00:52

Метапрограммирование шаблонов - это фантастика (вся ярость) - по крайней мере, я вижу область в переносимом низкоуровневом программировании. Люди использовали метапрограммирование препроцессора (уровень 1) с момента конкатенации токенов в ANSI-C, потенциал безграничен с механизмом шаблонов C++ - это все в моде; однако у нас мало инструментов, и это мешает работе.

Hassan Syed 15.12.2009 03:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
60
4
23 449
25

Ответы 25

Они анализируются компилятор, а не препроцессором, который запускает компилятор до.

Вот что об этом говорит MSDN: http://msdn.microsoft.com/en-us/library/aa903548(VS.71).aspx

Here are some problems with the macro:

  • There is no way for the compiler to verify that the macro parameters are of compatible types.
  • The macro is expanded without any special type checking.
  • The i and j parameters are evaluated twice. For example, if either parameter has a postincremented variable, the increment is performed two times.
  • Because macros are expanded by the preprocessor, compiler error messages will refer to the expanded macro, rather than the macro definition itself. Also, the macro will show up in expanded form during debugging.

Если вам этого мало, я не знаю, что.

Ссылка MSDN использует шаблон для Min, который в значительной степени является «плохим примером». См. Статью Скотта Мейера о шаблонах для Min / Max. aristeia.com/Papers/C++ReportColumns/jan95.pdf

Roddy 08.10.2008 02:33

Очевидно, вы технически правы, но утверждение, что один обрабатывается препроцессором, а другой - компилятором, не объясняет, почему один лучше другого.

Roel 08.10.2008 12:41

@Roddy Ты нечестен. Min как шаблон довольно прост для понимания в его несовершенном состоянии и предлагает лучшую защиту, чем макросы. У Александреску есть решение проблемы мин / макс, но оно довольно сложное, слишком сложное на мой вкус.

rlerallut 09.10.2008 01:21

@Roel Ну ... Вот почему я цитирую MSDN. Они довольно ясны: проверка типов, защита двойного приращения, сообщения об ошибках. Они все происходят из-за того, что он обрабатывается в компиляторе, вы не могу делаете это в препроцессоре. Кого волнует, что шаблоны являются полным по Тьюрингу языком?

rlerallut 09.10.2008 01:24

@rlerallut - Да, Мин прост для понимания, но это также НЕ ПОЛЕЗНО по причинам, указанным SM. Как он говорит: «Злость всего этого в том, что мы говорим здесь о функции max! Как такая концептуально простая функция может вызывать столько проблем?»

Roddy 10.10.2008 18:29

@Roddy Вся эта статья Скотта Мейера не имеет полного смысла. Если вы хотите получить возврат ссылки из функции min (), тогда оба ее аргумента должны иметь один и тот же тип (и модификатор const) на языке строгого типа, таком как C++, поскольку по определению вы не знаете во время компиляции, на какой аргумент ссылается ссылка будет возвращен. Мне кажется, он пытался решить искусственную проблему, созданную для аргументации.

Edy 20.04.2018 17:56

@Roddy Десять лет спустя у нас теперь есть решение проблемы дедукции min, std :: type_identity. template <typename T> T& min(T&, std::type_identity_t<T&>)

Caleth 14.08.2019 11:56

Первые 3 маркера здесь могут быть полностью смягчены / устранены с шаблоном OUT, используя выражения инструкции gcc с C++ static_asserts для проверки всех типов, переданных в макрос или выражение оператора gcc / clang. Другими словами, в современном C++ единственная действительная точка, остающаяся против макросов, - это последняя: ...compiler error messages will refer to the expanded macro, rather than the macro definition itself. Also, the macro will show up in expanded form during debugging..

Gabriel Staples 25.04.2020 20:30

Шаблоны предлагают некоторую степень безопасности типов.

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

Aaron 09.10.2008 07:11

Макросы - это механизм подстановки текста.

Шаблоны - это функциональный полный по Тьюрингу язык, который выполняется во время компиляции и интегрирован в систему типов C++. Вы можете думать о них как о механизме подключаемых модулей для языка.

На самом деле это не объясняет существенные различия. "Разве яблоки не просто апельсины?" - Плохой ответ: «Нет, апельсины - это цитрусовые, а яблоки производит Malus domestica». - Хороший ответ: «Нет, апельсины оранжевые и полны кислого сока с небольшим количеством мякоти, а яблоки красные или зеленые и содержат гораздо более плотную мякоть и меньшее количество более сладкого сока».

user253751 25.08.2020 17:38

Шаблоны безопасны по типу. С помощью определений у вас может быть код, который компилируется, но все равно работает некорректно.

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

С макросами всегда есть шанс, что какое-то выражение будет вычислено дважды. Представьте, что вы передаете что-то вроде ++ x в качестве параметра.

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

Шаблоны C++ похожи на макросы Lisp (не макросы C) в том, что они работают с уже проанализированной версией кода и позволяют генерировать произвольный код во время компиляции. К сожалению, вы программируете в чем-то похожем на необработанное лямбда-исчисление, поэтому продвинутые методы, такие как циклы, довольно громоздки. Для всех кровавых подробностей см. Генеративное программирование Крыштофа Чарнецкого и Ульриха Эйзенекера.

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

За исключением того, что всякий раз, когда я смотрю на FQA, я понимаю, что он действительно не понимает, о чем говорит. Многие из его жалоб связаны с неправильным использованием C++.

David Thornley 09.09.2009 00:50

Шаблоны могут делать гораздо больше, чем препроцессор макросов.

Например. есть специализации шаблона: Если этот шаблон создан с этим типом или константой, то не используйте реализацию по умолчанию, а вот эту ...

... шаблоны могут требовать, чтобы некоторые параметры были одного типа и т. д.


Вот некоторые источники, на которые вы, возможно, захотите посмотреть:

  • Шаблоны C++ от Вандервурде и Джоссутиса. Это лучшая и самая полная книга о шаблонах, которую я знаю.
  • Библиотека ускорения почти полностью состоит из определений шаблонов.

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

Да, это одно и то же: инструменты генерации кода.

Макросы - это примитивная форма, без особого применения компилятора (например, выполнение объектов в C - это можно сделать, но это некрасиво). Шаблоны являются более продвинутыми и имеют намного лучшую проверку типов компилятора, сообщения об ошибках и т. д.

Однако у каждого есть сильные стороны, которых нет у другого.

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

С другой стороны, с помощью шаблонов можно делать действительно ЗАБЫВНЫЕ вещи, которые невозможны с макросами. Например:

template<int d,int t> class Unit
{
    double value;
public:
    Unit(double n)
    {
        value = n;
    }
    Unit<d,t> operator+(Unit<d,t> n)
    {
        return Unit<d,t>(value + n.value);
    }
    Unit<d,t> operator-(Unit<d,t> n)
    {
        return Unit<d,t>(value - n.value);
    }
    Unit<d,t> operator*(double n)
    {
        return Unit<d,t>(value * n);
    }
    Unit<d,t> operator/(double n)
    {
        return Unit<d,t>(value / n);
    }
    Unit<d+d2,t+t2> operator*(Unit<d2,t2> n)
    {
        return Unit<d+d2,t+t2>(value * n.value);
    }
    Unit<d-d2,t-t2> operator/(Unit<d2,t2> n)
    {
        return Unit<d-d2,t-t2>(value / n.value);
    }
    etc....
};

#define Distance Unit<1,0>
#define Time     Unit<0,1>
#define Second   Time(1.0)
#define Meter    Distance(1.0)

void foo()
{
   Distance moved1 = 5 * Meter;
   Distance moved2 = 10 * Meter;
   Time time1 = 10 * Second;
   Time time2 = 20 * Second;
   if ((moved1 / time1) == (moved2 / time2))
       printf("Same speed!");
}

Шаблон позволяет компилятору динамически создавать и использовать типобезопасные экземпляры шаблона на лету. Компилятор фактически выполняет математические вычисления параметров шаблона во время компиляции, создавая отдельные классы там, где это необходимо для каждого уникального результата. Существует подразумеваемый тип Unit <1, -1> (расстояние / время = скорость), который создается и сравнивается в условном выражении, но никогда явно не объявляется в коде.

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

Я имел некоторое представление о том, что вы пытались сделать, пока не добрался до «Unit <d + d2, t + t2>», когда потерял сюжет. Можете ли вы объяснить, что он пытается сделать и какие преимущества он дает по сравнению с "typedef double Distance" / "typedef double Time", которые, казалось бы, дают тот же результат?

Roddy 08.10.2008 02:40

Объявите две переменные: Distance d; Время t; Если расстояние и время удваиваются, утверждение (d = t) и выражение (d == t) действительны. Шаблон предотвращает это - обеспечивая безопасность типов для числовых значений.

Jeff B 08.10.2008 19:23

Ах! Спасибо. Я НИКОГДА не смог бы сделать это для себя!

Roddy 08.10.2008 22:49

@@ Roddy: +1 за «Сделай вывод»

Chubsdad 16.09.2010 07:27

«Макросы могут быть очень полезны для встраивания статических таблиц структурированных данных в ваш код». - это не то, чего не могут сделать шаблоны. ;) Есть вещи, которые шаблоны делать не могут, но в основном это реификация; создание новых токенов и т. д.

Yakk - Adam Nevraumont 27.02.2017 21:40

Довольно хороший пример того, что не могут делать функции шаблона, - это функции регистрации, содержащие информацию о строке, где они были вызваны в коде: #define log(...) someLoggingFunction(__LINE__, __VA_ARGS__)

Aconcagua 15.08.2019 07:59

Я все еще полностью потерялся с материалом <d+d2,t+t2>. Ваш комментарий объясняет, что это какие, но не как. @JeffB, пожалуйста, объясните это так хорошо в своем ответе, что даже я может это понять! (Это устанавливает действительно высокую планку).

Gabriel Staples 25.04.2020 22:33

@JeffB, сделайте свой пример полным и работоспособным. Конечно, вам что-то не хватает, поскольку d2 и t2 никогда не определяются. Ошибка: main.cpp:41:12: error: ‘d2’ was not declared in this scope. См. Мой пример здесь; пожалуйста, сделайте его работоспособным в соответствии с тем, что вы пытаетесь продемонстрировать: onlinegdb.com/B12lMGzKI.

Gabriel Staples 25.04.2020 22:44

@GabrielStaples Я внес некоторые правки, чтобы код можно было компилировать, надеюсь, он скоро будет одобрен. Этот шаблон обеспечивает анализ измерений с использованием различных специализаций шаблона. Например, значение метров будет иметь тип Unit <1,0>, секунды - Unit <0,1>, м / с - Unit <1, -1> и т. д. Таким образом вы можете гарантировать, что разные единицы измерения не складываются и не вычитаются друг с другом (потому что они будут иметь несовместимые типы), а умножение или деление таких значений приведет к созданию нового типа с соответствующими размерами. (Как и в примере, Unit <1,0> / Unit <0,1> => Unit <1, -1>)

Balázs Kovacsics 15.06.2020 17:32

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

template <typename T>
void func(T t)
{
  T make_another = t;

Кто-то может возразить, что предстоящий оператор typeof может исправить это, но даже он не может разбить другие шаблоны:

template <typename T>
void func(container<T> c)

или даже:

template <tempate <typename> class Container, typename T>
void func(Container<T> ct)

Я также считаю, что предмет специализации был недостаточно освещен. Вот простой пример того, чего не могут делать макросы:

template <typename T>
T min(T a, T B)
{
  return a < b ? a : b;
}

template <>
char* min(char* a, char* b)
{
  if (strcmp(a, b) < 0)
    return a;
  else
    return b;
}

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

Это не столько ответ, сколько следствие уже изложенных ответов.

Работая с учеными, хирургами, художниками-графиками и другими людьми, которым нужно программировать, но которые не являются и никогда не будут профессиональными разработчиками программного обеспечения на полную ставку, я вижу, что макросы легко понимаются случайным программистом, в то время как шаблоны, похоже, требуют более высокого Уровень абстрактного мышления возможен только при более глубоком и постоянном опыте программирования на C++. Требуется много экземпляров работы с кодом, где шаблоны являются полезной концепцией, чтобы концепция приобрела достаточный смысл для использования. Хотя это можно сказать о любой языковой функции, количество опыта работы с шаблонами представляет собой больший пробел, чем обычный программист-специалист может получить от своей повседневной работы.

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

Хотя параметры шаблона проверяются по типу и у шаблонов есть много преимуществ перед макросами, шаблоны очень похожи на макросы в том, что они по-прежнему основаны на подстановке текста. Компилятор не будет проверять, имеет ли код шаблона какой-либо смысл, пока вы не предоставите ему параметры типа для замены. Visual C++ не жалуется на эту функцию, пока вы ее не вызываете:

template<class T>
void Garbage(int a, int b)
{
    fdsa uiofew & (a9 s) fdsahj += *! wtf;
}

Редактировать: Этот пример применим только к Visual C++. В стандарт C++ код вашего шаблона фактически анализируется в дерево синтаксиса до того, как шаблон когда-либо будет использован, поэтому пример принимается VC++, но не GCC или Clang. (Я узнал это, когда попытался перенести код VC++ в GCC и столкнулся с сотнями синтаксических ошибок в моих неспециализированных шаблонах.) Однако синтаксическое дерево все еще не обязательно имеет смысл семантически. Независимо от компилятора, проверка типа в теле не выполняется, пока вы не создадите экземпляр шаблона, предоставив <template arguments>.

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

Он компилирует эту функцию только при необходимости. Поэтому никаких ошибок не выдает.

Partial 14.09.2009 02:17

Это не функция. Garbage<int> - это функция, которая не компилируется.

Caleth 14.08.2019 11:42

Разработчики C++ часто называют шаблоны функций «функциями».

Qwertie 24.09.2019 20:02

На мой взгляд, макросы - плохая привычка для C. Хотя они могут быть полезны для некоторых, я не вижу в них реальной необходимости, когда есть определения типов и шаблоны. Шаблоны являются естественным продолжением объектно-ориентированного программирования. Вы можете сделать гораздо больше с помощью шаблонов ...

Учти это...

int main()
{
    SimpleList<short> lstA;
    //...
    SimpleList<int> lstB = lstA; //would normally give an error after trying to compile
}

Чтобы выполнить преобразование, вы можете использовать что-то, что называется конструктором преобразования и конструктором последовательности (см. Конец) в довольно полном примере для списка:

#include <algorithm>

template<class T>
class SimpleList
{
public:
    typedef T value_type;
    typedef std::size_t size_type;

private:
    struct Knot
    {
        value_type val_;
        Knot * next_;
        Knot(const value_type &val)
        :val_(val), next_(0)
        {}
    };
    Knot * head_;
    size_type nelems_;

public:
    //Default constructor
    SimpleList() throw()
    :head_(0), nelems_(0)
    {}
    bool empty() const throw()
    { return size() == 0; }
    size_type size() const throw()
    { return nelems_; }

private:
    Knot * last() throw() //could be done better
    {
        if (empty()) return 0;
        Knot *p = head_;
        while (p->next_)
            p = p->next_;
        return p;
    }

public:
    void push_back(const value_type & val)
    {
        Knot *p = last();
        if (!p)
            head_ = new Knot(val);
        else
            p->next_ = new Knot(val);
        ++nelems_;
    }
    void clear() throw()
    {
        while(head_)
        {
            Knot *p = head_->next_;
            delete head_;
            head_ = p;
        }
        nelems_ = 0;
    }
    //Destructor:
    ~SimpleList() throw()
    { clear(); }
    //Iterators:
    class iterator
    {
        Knot * cur_;
    public:
        iterator(Knot *p) throw()
        :cur_(p)
        {}
        bool operator==(const iterator & iter)const throw()
        { return cur_ == iter.cur_; }
        bool operator!=(const iterator & iter)const throw()
        { return !(*this == iter); }
        iterator & operator++()
        {
            cur_ = cur_->next_;
            return *this;
        }
        iterator operator++(int)
        {
            iterator temp(*this);
            operator++();
            return temp;
        }
        value_type & operator*()throw()
        { return cur_->val_; }
        value_type operator*() const
        { return cur_->val_; }
        value_type operator->()
        { return cur_->val_; }
        const value_type operator->() const
        { return cur_->val_; }
    };
    iterator begin() throw()
    { return iterator(head_); }
    iterator begin() const throw()
    { return iterator(head_); }
    iterator end() throw()
    { return iterator(0); }
    iterator end() const throw()
    { return iterator(0); }
    //Copy constructor:
    SimpleList(const SimpleList & lst)
    :head_(0), nelems_(0)
    {
        for(iterator i = lst.begin(); i != lst.end(); ++i)
            push_back(*i);
    }
    void swap(SimpleList & lst) throw()
    {
        std::swap(head_, lst.head_);
        std::swap(nelems_, lst.nelems_);
    }
    SimpleList & operator=(const SimpleList & lst)
    {
        SimpleList(lst).swap(*this);
        return *this;
    }
    //Conversion constructor
    template<class U>
    SimpleList(const SimpleList<U> &lst)
    :head_(0), nelems_(0)
    {
        for(typename SimpleList<U>::iterator iter = lst.begin(); iter != lst.end(); ++iter)
            push_back(*iter);
    }
    template<class U>
    SimpleList & operator=(const SimpleList<U> &lst)
    {
        SimpleList(lst).swap(*this);
        return *this;
    }
    //Sequence constructor:
    template<class Iter>
    SimpleList(Iter first, Iter last)
    :head_(0), nelems_(0)
    {
        for(;first!=last; ++first)
            push_back(*first);


    }
};

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

Ответ такой длинный, что я не могу подытожить все, но:

  • например, макросы не обеспечивают безопасность типов, в то время как шаблоны функций делают:
    Компилятор не может проверить, являются ли параметры макроса совместимыми типами - также во время создания экземпляра шаблона функции компилятор знает, определяет ли int или floatoperator +.
  • шаблоны открывают дверь для метапрограммирования (короче говоря, оценки вещей и принятия решения во время компиляции):
    Во время компиляции можно узнать, является ли тип целым или с плавающей запятой; будь то указатель или квалифицированный констант, и т. д. см. «характеристики типа» в готовящемся к выпуску C++ 0x.
  • шаблоны классов имеют частичную специализацию
  • шаблоны функций имеют явную полную специализацию, в вашем примере add<float>(5, 3); может быть реализован иначе, чем add<int>(5, 3);, что невозможно с макросами
  • макрос не имеет области видимости
  • #define min(i, j) (((i) < (j)) ? (i) : (j)) - параметры i и j оцениваются дважды. Например, если какой-либо параметр имеет постинкрементную переменную, приращение выполняется два раза.
  • поскольку макросы раскрываются препроцессором, сообщения об ошибках компилятора будут относиться к расширенному макросу, а не к самому определению макроса. Кроме того, макрос будет отображаться в развернутом виде во время отладки.
  • так далее...

Примечание. В некоторых редких случаях я предпочитал полагаться на макросы с переменным числом аргументов, потому что до тех пор, пока C++ 0x не станет мейнстримом, такой вещи, как вариативные шаблоны, не существует.C++ 11 в прямом эфире.

Использованная литература:

У нас тут сердитый противник ;-) Я также понятия не имею, за что меня отвергли ;-)

Michael Krelin - hacker 14.12.2009 21:29

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

Shaggy Frog 14.12.2009 21:30

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

Michael Krelin - hacker 14.12.2009 21:38

Теперь, когда вопросы были объединены, я чувствую себя глупо - мои комментарии, кажется, обвиняют автора в том, чего он никогда не делал ;-)

Michael Krelin - hacker 15.12.2009 00:34

Кто-нибудь, пожалуйста, объясните, почему мой вопрос давнишней давности внезапно сотрясает всех по клеткам? Думаю, здесь я теряю сюжет ...

Roddy 15.12.2009 00:40

О, и Грегори, +1 за полезный ответ с некоторыми хорошими моментами.

Roddy 15.12.2009 00:41

видимо они слили новый вопрос с вашим старым :)

Gregory Pakosz 15.12.2009 00:43

@Gregory, спасибо - это многое объясняет. [И нет, я не фантомный отрицатель. Лучше чем заняться со своим временем!]

Roddy 15.12.2009 00:47

я удаляю свои комментарии, я не хочу, чтобы ты был целью

Gregory Pakosz 15.12.2009 00:55

одна быстрая придирка ... макросы делать имеют область видимости, но их область действия (на языке макросов C) является явной. вы можете #undef их прекратить.

Woodrow Douglass 14.07.2016 15:24

Это неверно: there is no way for the compiler to verify that the macro parameters are of compatible types. См. здесь: в современном C++ вы можете использовать static_assert для проверки типов, передаваемых макросу. templates open the door for metaprogramming. Так что сделайте макросы. Пуля 4 тоже неверна; в макросе вы можете действовать, например, на основе std::is_same<decltype(my_variable), uint64_t>::value. macro don't have any scope: неправильно: переносить макрос в do {} while (0) - очень часто. Теперь они всегда внутренняя сфера.

Gabriel Staples 25.04.2020 20:44

Двойная оценка решается с помощью выражения операторов gcc / clang. Остается только 1 или 2 из ваших пуль, оставшихся действительными.

Gabriel Staples 25.04.2020 20:45

НЕТ. Один простой встречный пример: шаблоны подчиняются пространствам имен, макрос игнорирует пространства имен (поскольку они являются операторами препроцессора).

namespace foo {
    template <class NumberType>
    NumberType add(NumberType a, NumberType b)
    {
        return a+b;
    }

    #define ADD(x, y) ((x)+(y))
} // namespace foo

namespace logspace 
{
    // no problemo
    template <class NumberType>
    NumberType add(NumberType a, NumberType b)
    {
        return log(a)+log(b);
    }

    // redefintion: warning/error/bugs!
    #define ADD(x, y) (log(x)+log(y))

} // namespace logspace

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

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

template <typename T>
struct is_void
{
    static const bool value = false;
}

template <>
struct is_void<void>
{
    static const bool value = true;
}

Что само по себе является лишь одним примером много вещей, которые ты можешь сделать. Сами шаблоны являются полными по Тьюрингу.

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

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

Это действительно большое дело и предотвращает множество ошибок.

Этот ответ призван пролить свет на препроцессор C и то, как его можно использовать для общего программирования.


В некотором смысле они допускают схожую семантику. Препроцессор C использовался для включения общих структур данных и алгоритмов (см. конкатинация токена). Однако, без учета каких-либо других функций шаблонов C++, он превращает общую игру программирования в ЛОТ ЧИСТЕ для чтения и реализации.

Если кто-то хочет увидеть в действии хардкорное базовое программирование на C, прочтите исходный код Libevent - это также упоминается здесь. Реализован обширный набор контейнеров / алгоритмов, и это делается в заголовочном файле НЕ ЗАМУЖЕМ (очень читаемом). Я действительно восхищаюсь этим, код шаблона C++ (который я предпочитаю из-за других его атрибутов) ОЧЕНЬ многословен.

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

Для некоторых реальных примеров прочтите Современное программирование на C++ Андре Александреску или C++ Метапрограммирование Дэйва Абрахамса и Алексея Гуртового. Практически ничего из того, что написано в обеих книгах, не может быть смоделировано с помощью препроцессора в большей степени, чем крайне минимальная.

Обновлено: Что касается typename, требование довольно простое. Компилятор не всегда может определить, относится ли зависимое имя к типу или нет. Использование typename явно сообщает компилятору, что он ссылается на тип.

struct X { 
    int x;
};

struct Y {
    typedef long x;
};

template <class T>
class Z { 
    T::x;
};

Z<X>; // T::x == the int variable named x
Z<Y>; // T::x == a typedef for the type 'long'

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

Ключевое слово typename используется для включения контекстно-свободных вложенных определений типов. Они были необходимы для метода признаков, который позволяет добавлять метаданные к типам (особенно встроенным типам, таким как указатель), это требовалось для записи STL. В остальном ключевое слово typename совпадает с ключевым словом class.

Разница между ключевыми словами typename и class.
legends2k 17.10.2013 13:26

Попробуем примитивный пример. Рассматривать

#define min(a,b) ((a)<(b))?(a):(b)

вызывается как

c = min(a++,++b);

Конечно, реальная разница глубже, но этого должно быть достаточно, чтобы отбросить сходство с макросами.

Редактировать: И нет, вы не можете обеспечить безопасность типов с помощью макросов. Как бы вы реализовали безопасный тип min() для каждого типа, определяющего меньше, чем сравнение (например, operrator<)?

Прочтите мои ответы на ответы, это не тот ответ, который я ищу.

static_rtti 14.12.2009 21:30

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

just somebody 14.12.2009 21:34

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

Michael Krelin - hacker 14.12.2009 21:36

@ Просто кто-нибудь: Хорошо, напишите макрос min(a, b), который не выполняет двойную оценку аргумента, и вы не только подтвердите свою точку зрения, но и произведете на меня сильное впечатление. Я не думаю, что это возможно.

David Thornley 15.12.2009 00:25

Теперь, когда ответы перенесены на этот вопрос, они не выглядят как настоящие ;-)

Michael Krelin - hacker 15.12.2009 00:36

@hacker: У меня нет времени писать сейчас, но попроси, увидишь :)

static_rtti 15.12.2009 01:04

Чтобы было ясно, может быть несколько расширений языка C нестандартный, реализованных некоторыми популярными компиляторами (например, typeof в GCC), которые в сочетании может допускать макрос min(a, b), который не оценивает свои аргументы дважды. Также может быть неопределенное или определяемое реализацией поведение, допускающее такие реализации. Но нет способа универсальный и портативный сделать такой макрос, в чем здесь суть.

mtraceur 28.10.2019 21:24

Шаблоны понимать типы данных. Макросы нет.

Это означает, что вы можете делать что-то вроде следующего ...

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

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

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

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

Есть несколько основных проблем с макросами.

Во-первых, они не уважают объем или тип. Если у меня #define max(a, b)..., то всякий раз, когда в моей программе присутствует токен max, по какой-либо причине он будет заменен. Он будет заменен, если это имя переменной или глубоко внутри вложенных областей видимости. Это может вызвать труднодоступные ошибки компиляции. Напротив, шаблоны работают внутри системы типов C++. Имя шаблонной функции может быть повторно использовано внутри области видимости, и она не будет пытаться переписать имя переменной.

Во-вторых, макросы нельзя изменять. Шаблон std::swap обычно просто объявляет временную переменную и выполняет очевидные назначения, потому что это очевидный способ, который обычно работает. Это то, чем был бы ограничен макрос. Это было бы крайне неэффективно для больших векторов, поэтому у векторов есть специальный swap, который меняет местами ссылки, а не весь контент. (Это оказывается очень важным для тех вещей, которые среднестатистический программист на C++ не должен писать, но использует.)

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

Отличным примером мощи шаблонов является то, что изначально называлось Стандартной библиотекой шаблонов, которая входит в стандартную комплектацию как контейнеры, алгоритмы и итераторы. Посмотрите, как они работают, и попробуйте подумать, как бы вы заменили их макросами. Александр Степанов просмотрел большое количество языков для реализации своих идей STL и пришел к выводу, что C++ с шаблонами - единственный язык, на котором он будет работать.

Шаблоны интегрированы в язык и безопасны по типу.

Подскажите, как бы вы это сделали с помощью макросов. Это тяжелое метапрограммирование шаблонов.

https://thewikihow.com/video_0A9pYr8wevk

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

Другие вопросы по теме