【问题标题】:Are C++ Templates just Macros in disguise?C++ 模板只是变相的宏吗?
【发布时间】:2022-01-03 11:30:04
【问题描述】:

我用 C++ 编程已经有几年了,我也使用过很多 STL,并创建了几次自己的模板类来看看它是如何完成的。

现在我正在尝试将模板更深入地集成到我的 OO 设计中,一个烦人的想法不断地回到我的脑海:它们只是一个宏,真的……你可以使用 #defines 实现(相当丑陋的)auto_ptrs,如果你真的想的话。

这种对模板的思考方式有助于我理解我的代码实际上是如何工作的,但我觉得我一定是以某种方式错过了重点。宏意味着邪恶的化身,但“模板元编程”风靡一时。

那么,真正的区别是什么?以及模板如何避免#define 导致您陷入的危险,例如

  • 难以理解的编译器错误 出乎意料的地方?
  • 代码膨胀?
  • 难以追踪代码?
  • 设置调试器断点?

【问题讨论】:

  • 您为什么认为“模板元编程风靡一时”?
  • 不,他们不是。 :-) 而且您的问题很重要,因为模板无法解决您提到的任何问题。相反,它们有助于解决您方便忽略的一类完全不同的问题。问题以答案为前提,如果我有能力,我会因主观和争论而取消它的资格。
  • @Omnifarious - 这是对这个问题的评论,还是与我合并的评论?这个关于一年前的问题的大惊小怪让我头晕目眩……
  • 模板元编程很棒(风靡一时)——至少我看到了可移植低级编程的一个领域。自从 ANSI-C 中的标记连接以来,人们一直在使用预处理器(级别 1)元编程,C++ 的模板机制的潜力是无穷无尽的——它风靡一时;然而,我们缺乏工具,这给事情带来了阻碍。

标签: c++ templates macros


【解决方案1】:

答案很长,我无法总结所有内容,但:

  • 例如,宏不能确保类型安全,而函数模板可以:
    编译器无法验证宏参数的类型是否兼容——在函数模板被实例化时,编译器也知道是 int 还是 float 定义 operator +
  • 模板为元编程打开了大门(简而言之,在编译时评估事物并做出决定):
    在编译时可以知道一个类型是整型还是浮点型;无论是指针还是 const 限定等...see "type traits" in upcoming c++0x
  • 类模板具有部分特化
  • 函数模板具有明确的完全专业化,在您的示例中,add<float>(5, 3); 的实现方式可能与 add<int>(5, 3); 的实现方式不同,而这对于宏来说是不可能的
  • 宏没有任何作用域
  • #define min(i, j) (((i) < (j)) ? (i) : (j)) - ij 参数被评估两次。例如,如果任一参数具有后增量变量,则增量执行两次
  • 因为宏是由预处理器扩展的,编译器错误消息将引用扩展的宏,而不是宏定义本身。此外,宏将在调试期间以展开形式显示
  • 等等...

注意:在极少数情况下,我更喜欢依赖可变参数宏,因为在 c++0x 成为主流之前,不存在可变参数模板之类的东西。 C++11 已上线。

参考资料:

【讨论】:

  • 我们这里有一个愤怒的反对者 ;-) 我也不知道我被反对的原因是什么 ;-)
  • 您可能会被另一位希望他/她的答案首先排在列表顶部的受访者否定,这样更有可能将其标记为已接受的答案。
  • 因为他认为自己无法提出问题足以将问题归咎于没有给出他想要的答案的每个人。我几乎很想对这个问题投反对票,但我需要的是一种对这个人投反对票的方法;-)
  • 现在问题已经合并了,我觉得自己很愚蠢——我的 cmets 似乎在用他从未做过的事情指责发帖者 ;-)
  • 有人能解释一下为什么我的老问题突然让每个人的笼子都嘎嘎作响吗?我想我一定会失去这里的情节......
【解决方案2】:

这里有很多 cmets 试图区分宏和模板。

是的 - 它们都是同一个东西:代码生成工具。

宏是一种原始形式,没有太多的编译器强制执行(就像在 C 中做对象 - 可以做到,但它并不漂亮)。模板更高级,并且有更好的编译器类型检查、错误消息等。

然而,每个人都有对方没有的优势。

模板只能生成动态类类型 - 宏几乎可以生成您想要的任何代码(除了另一个宏定义)。宏对于将结构化数据的静态表嵌入到您的代码中非常有用。

另一方面,模板可以完成一些用宏无法实现的真正 FUNKY 的事情。例如:

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(距离 / 时间 = 速度)类型,它在条件内创建和比较,但从未在代码中显式声明。

显然,大学里有人定义了一个包含 40 多个参数(需要参考)的此类模板,每个参数代表不同的物理单元类型。考虑一下这种类的类型安全性,只是为了你的数字。

【讨论】:

  • 我知道你想做什么,直到我到达“Unit”,然后我失去了情节。你能解释一下它试图做什么以及它比“typedef double Distance”/“typedef double Time”有什么优势,这似乎给出了相同的结果?
  • 声明两个变量:距离d;时间t;如果距离和时间都是双精度数,则语句 (d = t) 和表达式 (d == t) 都有效。模板可以防止这种情况 - 为数值提供类型安全。
  • 啊!谢谢。我永远无法为自己推断出这一点!
  • @@Roddy:为“推断”+1
  • 模板函数不能做的一个很好的例子是记录包含在代码中被调用的行信息的函数:#define log(...) someLoggingFunction(__LINE__, __VA_ARGS__)
【解决方案3】:

虽然模板参数是经过类型检查的,并且模板比宏有许多优点,但模板与宏非常相似,因为它们仍然基于文本替换。编译器将不会验证您的模板代码是否有意义,直到您为其提供类型参数以进行替换。只要您没有实际调用它,Visual C++ 就不会抱怨这个函数:

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

编辑:此示例仅适用于 Visual C++。在 standard C++ 中,您的模板代码实际上在使用模板之前就被解析为语法树,因此该示例被 VC++ 接受,但不被 GCC 或 Clang 接受。 (当我尝试将 VC++ 代码移植到 GCC 并不得不处理我的非专业模板中的数百个语法错误时,我了解到这一点。)但是,语法树仍然不一定有任何意义语义。无论编译器如何,在您通过提供&lt;template arguments&gt; 实例化模板之前,不会在正文中进行类型检查。

因此,一般来说,对于模板设计接受的给定类型参数类别,您的模板代码是否能正常工作或编译成功是不可能的。

【讨论】:

  • 它只在需要时编译该函数。这就是为什么它不会给出任何错误。
  • 这不是函数。 Garbage&lt;int&gt; 是一个函数,编译失败。
  • C++ 开发人员通常将函数模板称为“函数”。
【解决方案4】:

模板集成在语言中并且是类型安全的。

告诉我你将如何使用宏来做到这一点。这是繁重的模板元编程。

https://www.youtube.com/watch?v=0A9pYr8wevk

我认为宏,AFAIK,不能像模板部分特化那样计算类型。

【讨论】:

    【解决方案5】:

    宏是一种文本替换机制。

    模板是一种功能性图灵完备语言,在编译时执行并集成到 C++ 类型系统中。您可以将它们视为该语言的插件机制。

    【讨论】:

    • 这并不能真正解释相关的差异。 “苹果不就是橙子吗?” -- 错误答案:“不,橙子是柑橘类水果,但苹果是由 Malus domestica 生产的。” -- 好的答案:“不,橙子是橙色的,富含酸汁和一些果肉,而苹果是红色或绿色的,果肉更密集,汁液更甜。”
    【解决方案6】:

    模板仅在最基本的功能上类似于宏。毕竟,模板作为宏的“文明”替代品被引入语言。但即使涉及到最基本的功能,相似之处也只是肤浅的。

    但是,一旦我们了解模板的更高级功能,例如专业化(部分或显式),与宏的任何明显相似性都会完全消失。

    【讨论】:

      【解决方案7】:

      宏存在一些基本问题。

      首先,他们不尊重范围或类型。如果我有#define max(a, b)...,那么只要我的程序中有令牌max,无论出于何种原因,它都会被替换。如果它是变量名或嵌套范围内的深处,它将被替换。这可能会导致难以发现的编译错误。相反,模板在 C++ 类型系统中工作。模板函数可以在作用域内重复使用其名称,并且不会尝试重写变量名称。

      第二,宏不能变化。模板std::swap 通常只会声明一个临时变量并进行明显的赋值,因为这是通常工作的明显方式。这就是宏的限制。这对于大型向量来说效率极低,因此向量有一个特殊的 swap 来交换引用而不是整个内容。 (事实证明,这对于普通 C++ 程序员不应该编写但确实使用的东西非常重要。)

      第三,宏不能进行任何形式的类型推断。首先你不能写一个通用的交换宏,因为它必须声明一个类型的变量,而且它不知道类型可能是什么。模板是类型感知的。

      模板强大的一个很好的例子是最初称为标准模板库,它在标准中作为容器、算法和迭代器。看看它们是如何工作的,并尝试考虑如何用宏替换它。 Alexander Stepanov 研究了多种语言来实现他的 STL 想法,并得出结论认为带有模板的 C++ 是唯一可以使用的语言。

      【讨论】:

        【解决方案8】:

        模板了解数据类型。宏不了解。

        这意味着您可以执行以下操作...

        • 定义可以采用任何数据类型的操作(例如,wrapping numbers),然后提供根据数据类型是整数还是浮点选择适当算法的特化
        • 在编译时确定数据类型的各个方面,允许使用 template deduction of array size 之类的技巧,Microsoft 将其用于 strcpy_s 及其同类的 C++ 重载

        此外,由于模板是类型安全的,因此有许多模板编码技术可以使用一些假设的高级预处理器来执行,但充其量是笨拙和容易出错的(例如,template template parameters,默认模板参数, Modern C++ Design 中讨论的策略模板)。

        【讨论】:

          【解决方案9】:

          此答案旨在阐明 C 预处理器以及如何将其用于泛型编程


          它们在某些方面是因为它们启用了一些相似的语义。 C 预处理器已用于启用通用数据结构和算法(请参阅token Concatination)。然而,在不考虑 C++ 模板的任何其他特性的情况下,它使整个通用编程游戏变得更清晰易于阅读和实现。

          如果有人想看到核心 C 的通用编程,请阅读libevent 源代码——这也提到了here。实现了大量的容器/算法,并在 SINGLE 头文件中完成(非常易读)。我真的很佩服这一点,C++ 模板代码(我更喜欢它的其他属性)非常冗长。

          【讨论】:

            【解决方案10】:

            让我们尝试原始示例。考虑

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

            调用为

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

            当然,真正的区别更深,但这应该足以放弃与宏的相似之处。

            编辑:不,宏不能确保类型安全。对于定义小于比较的每个类型(即operrator&lt;),您将如何实现类型安全min()

            【讨论】:

            • 阅读我的答案,这不是我要找的答案。
            • 虽然你写的宏做了一些意想不到的事情,但它可以被写成只评估一次参数。 (没有投反对票)
            • 只有一个,我怀疑它可以。即使可以,您也可以使用相同的想法提出更好的示例。
            • @just someone:好的,写一个不重复评估参数的min(a, b) 宏,你不仅会确立你的观点,而且会给我留下深刻的印象。我不认为它可以做到。
            • 现在答案已迁移到这个问题,它们看起来不像真正的答案;-)
            【解决方案11】:

            不,这是不可能的。对于 T 的容器之类的一些事情,预处理器(勉强)足够了,但对于模板可以做的很多其他事情,它根本不够。

            对于一些真实的例子,请阅读 Andre Alexandrescu 的 Modern C++ Programming,或 Dave Abrahams 和 Aleksey Gurtovoy 的 C++ Metaprogramming。这两本书中所做的几乎所有事情都不能用预处理器模拟到极小的程度。

            编辑:就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 告诉编译器一个特定的名称旨在引用一个类型,而不是变量/值,因此(例如)您可以定义该类型的其他变量。

            【讨论】:

              【解决方案12】:

              。一个简单的反例:模板遵守命名空间,宏忽略命名空间(因为它们是预处理器语句)。

              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
              

              【讨论】:

                【解决方案13】:

                提供 typename 关键字以启用上下文无关的嵌套 typdef。这些是允许将元数据添加到类型(尤其是内置类型,如指针)的特征技术所需要的,这是编写 STL 所必需的。 typename 关键字在其他方面与 class 关键字相同。

                【解决方案14】:
                • 模板是类型安全的。
                • 模板化的对象/类型可以命名空间,成为类的私有成员等。
                • 模板化函数的参数不会在整个函数体中复制。

                这些确实很重要,可以防止大量错误。

                【讨论】:

                  【解决方案15】:

                  在非常基本的层面上,是的,模板只是宏替换。但是这样想你会跳过很多的事情。

                  考虑模板专业化,据我所知,您无法使用宏进行模拟。这不仅允许特定类型的特殊实现,而且是模板元编程的关键部分之一:

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

                  这本身只是many things you can do 的一个例子。模板本身是图灵完备的。

                  这忽略了非常基本的东西,例如范围、类型安全,而且那个宏更混乱。

                  【讨论】:

                    【解决方案16】:

                    在我看来,宏是 C 中的一个坏习惯。虽然它们对某些人有用,但当有 typedef 和模板时,我认为它们并没有真正的需要。模板是面向对象编程的自然延续。您可以使用模板做更多事情...

                    考虑一下……

                    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);
                    
                    
                        }
                    };
                    

                    看看information from cplusplus.com on templates!您可以使用模板来执行所谓的特征,使用的特征具有类型等的文档。您可以使用模板做更多的事情,而不是使用宏!

                    【讨论】:

                      【解决方案17】:

                      这不是一个答案,而是已经陈述的答案的结果。

                      与需要编程的科学家、外科医生、图形艺术家和其他人一起工作——但他们不是也永远不会是专业的全职软件开发人员——我发现偶尔的程序员很容易理解宏,而模板似乎需要更高水平的抽象思维,只有通过更深入和持续的 C++ 编程经验才能实现。它需要许多使用模板作为有用概念的代码的实例,才能使该概念足够有意义以供使用。虽然这可以说是任何语言功能,但模板的经验量与专业的临时程序员可能从他们的日常工作中获得的经验相比存在更大的差距。

                      普通的天文学家或电子工程师可能很好地理解宏,甚至可能理解为什么应该避免使用宏,但对于日常使用的模板却无法很好地理解。在这种情况下,宏实际上更好。当然,也有很多例外。一些物理学家围绕专业软件工程师转圈,但这并不典型。

                      【讨论】:

                        【解决方案18】:

                        没有提到的是模板函数可以推导参数类型。

                        模板 无效函数(T t) { T make_another = t;

                        有人可能会争辩说,即将推出的“typeof”运算符可以解决这个问题,但即使它也无法分解其他模板:

                        模板 无效函数(容器 c)

                        甚至:

                        模板 类容器,typename T> 无效函数(容器 ct)

                        我也觉得专业化的主题没有得到足够的覆盖。下面是一个宏不能做的简单例子:

                        模板 T min(T a, T B) { 返回 a char* min(char* a, char* b) { 如果 (strcmp(a, b)

                        空间太小,无法进入类型专业化,但就我而言,你可以用它做些什么,令人兴奋。

                        【讨论】:

                          【解决方案19】:

                          模板可以做的比宏预处理器做的要多。

                          例如有模板特化:如果这个模板是用这种类型或常量实例化的,那么不要使用默认实现,但是这里是这个......

                          ...模板可以强制某些参数属于同一类型,等等...


                          这里有一些你可能想看看的来源:

                          • C++ templates Vandervoorde 和 Jossutis。这是我所知道的关于模板的最好、最完整的书。
                          • The boost library 几乎完全由模板定义组成。

                          【讨论】:

                            【解决方案20】:

                            如果您正在寻找对该主题更深入的处理,我可以将您转至每个人的favorite C++ hater。这个人知道和讨厌的 C++ 比我想象的要多。这同时使 FQA 非常具有煽动性和极好的资源。

                            【讨论】:

                            • 除了每当我看 FQA 时我意识到他真的不知道他在说什么。他的许多抱怨都是由于滥用 C++。
                            【解决方案21】:

                            C++ 模板有点像 Lisp 宏(不是 C 宏),因为它们对已经解析的代码版本进行操作,并且它们允许您在编译时生成任意代码。不幸的是,您正在使用类似于原始 Lambda 演算的东西进行编程,因此循环等高级技术有点麻烦。有关所有血腥细节,请参阅 Krysztof Czarnecki 和 Ulrich Eisenecker 的生成式编程

                            【讨论】:

                              【解决方案22】:

                              模板可以放在命名空间中,也可以是类的成员。宏只是一个预处理步骤。基本上,模板是语言的一流成员,可以与其他所有东西配合得很好(更好?)。

                              【讨论】:

                                【解决方案23】:

                                模板是类型安全的。使用定义,您可以获得可以编译但仍无法正常工作的代码。

                                宏在编译器到达代码之前展开。这意味着您会收到扩展代码的错误消息,并且调试器只能看到扩展版本。

                                使用宏,某些表达式总是有可能被计算两次。想象一下将 ++x 之类的东西作为参数传递。

                                【讨论】:

                                  【解决方案24】:

                                  模板提供一定程度的类型安全性。

                                  【讨论】:

                                  • 这就像在说“函数比宏提供了某种程度的类型安全”。虽然在技术上是正确的,但这并不是全部答案,当然也不是规定性的。
                                  【解决方案25】:

                                  它们由编译器解析,而不是由在编译器之前运行的预处理器解析。

                                  MSDN 对此是这么说的: http://msdn.microsoft.com/en-us/library/aa903548(VS.71).aspx

                                  以下是宏的一些问题:

                                  • 编译器无法验证宏参数的类型是否兼容。
                                  • 宏在扩展时没有任何特殊的类型检查。
                                  • i 和 j 参数被计算两次。例如,如果任一参数具有后自增变量,则会执行两次自增。
                                  • 因为宏是由预处理器扩展的,编译器错误消息将引用扩展的宏,而不是宏定义本身。此外,宏将在调试期间以展开形式显示。

                                  如果这对你来说还不够,我不知道是什么。

                                  【讨论】:

                                  • MSDN 链接使用 Min 的模板,这几乎是终极的“坏例子”。请参阅 Scott Meyer 关于 Min/Max 模板的论文。 aristeia.com/Papers/C++ReportColumns/jan95.pdf
                                  • 显然您在技术上是正确的,但是说一个由预处理器处理而另一个由编译器处理并不能说明为什么一个比另一个更好。
                                  • @Roddy 你不公平。 Min 作为模板在其不完善的状态下相当容易理解,并且提供比宏更好的保护。 Alexandrescu 有一个解决最小/最大问题的方法,但它非常复杂,对我来说太复杂了。
                                  • @Roel 好吧...这就是我引用 MSDN 的原因。它们相当明确:类型检查、双增量保护、错误消息。它们来自于它在编译器中处理的事实,你不能在预处理器中进行。谁在乎模板是图灵完备的语言?
                                  • @rlerallut - 是的,Min 很容易理解,但由于 SM 提到的原因,它也没有用。正如他所说:“问题在于我们在这里讨论的是 max 函数!如此概念上简单的函数怎么会引起这么多麻烦?”
                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2012-05-18
                                  • 1970-01-01
                                  • 2015-05-07
                                  相关资源
                                  最近更新 更多