有没有人在现实生活中使用模板元编程?

时间:2008-09-15 14:39:32

标签: c++ templates template-meta-programming

我在5年多前发现了template metaprogramming并且在阅读Modern C++ Design时获得了巨大的成功,但我从未发现在现实生活中使用它的机会。

曾在实际代码中使用过这种技术吗?

  

Boost的贡献者无需申请; o)

15 个答案:

答案 0 :(得分:26)

我曾经在C ++中使用模板元编程来实现一种称为“符号扰动”的技术,用于处理几何算法中的退化输入。通过将算术表达式表示为嵌套模板(即基本上通过手工写出解析树),我能够将所有表达式分析交给模板处理器。

使用模板执行此类操作比使用对象编写表达式树并在运行时执行分析更有效。它更快,因为修改后的(扰动的)表达式树可以与优化程序在与其余代码相同的级别上使用,因此您可以在表达式中获得优化的全部好处,但也可以(在可能的情况下)在表达式和周围的代码。

当然,您可以通过为表达式实现一个小型DSL(特定于域的语言)并将已翻译的C ++代码粘贴到常规程序中来完成同样的事情。这样可以获得所有相同的优化优势,并且更加清晰 - 但需要权衡的是,您必须维护解析器。

答案 1 :(得分:21)

我发现在Modern C ++ Design中描述的策略在两种情况下非常有用:

  1. 当我正在开发一个我期望重用的组件时,但方式略有不同。 Alexandrescu关于使用策略来反映设计的建议在这里非常合适 - 它帮助我解决了类似的问题,“我可以通过后台线程做到这一点,但如果有人后来想要在时间片中做到这一点怎么办?”好吧,我只是编写我的类来接受ConcurrencyPolicy并实现我现在需要的那个。然后至少我知道跟在我后面的人可以在需要时编写并插入新的策略,而不必完全重写我的设计。警告:有时我必须自己统治,否则这可能失控 - 记住YAGNI原则!

  2. 当我试图将几个类似的代码块重构为一个时。通常,代码将被轻微地复制粘贴和修改,因为否则会有太多if / else逻辑,或者因为涉及的类型太不同。我发现策略通常允许使用传统逻辑或多重继承的干净的一刀切版本。

答案 2 :(得分:13)

我在游戏图形代码的内部循环中使用它,你需要某种程度的抽象和模块化但不能支付分支或虚拟调用的成本。总的来说,这是一个比手写的特殊功能扩散更好的解决方案。

答案 3 :(得分:12)

模板元编程和表达模板在科学界变得越来越流行,作为优化方法,在保持一些抽象的同时将一些计算工作卸载到编译器上。结果代码更大,可读性更低,但我使用这些技术来加速FEM库中的线性代数库和正交方法。

对于特定应用程序的阅读,Todd Veldhuizen是该领域的一个重要名称。 Daoqi Yang的一本畅销书是C++ and Object Oriented Numeric Computing for Scientists and Engineers

答案 4 :(得分:10)

在编写c ++ 时,模板元编程是一种很棒的强大技术。我在自定义解决方案中使用过一段时间,但通常不太优雅的旧式c ++解决方案更容易通过代码审查,更容易为其他用户维护。

但是,在编写可重用的组件/库时,我已经从模板元编程中获得了很多好处。我不是说Boost的一些东西只是一些可以经常重复使用的小部件。

我将TMP用于单例系统,用户可以在其中指定所需的单例类型。界面非常基础。在它下面是由重型TMP提供动力。

template< typename T >
T& singleton();

template< typename T >
T& zombie_singleton();

template< typename T >
T& phoenix_singleton();

另一个成功的用途是简化我们的IPC层。它采用经典的OO风格打造。每条消息都需要从抽象基类派生并覆盖一些序列化方法。没有什么太极端,但它产生了很多锅炉板代码。

我们向它扔了一些TMP,并为仅包含POD数据的消息的简单情况自动生成所有代码。 TMP消息仍然使用OO后端,但它们大大减少了锅炉板代码的数量。 TMP还用于生成消息vistor。随着时间的推移,我们所有的消息都迁移到了TMP方法。为消息传递构建一个简单的POD结构更容易,代码更少,并且添加少量(可能是3行)获取TMP生成类所需的行比导出一条新消息以在IPC中发送常规类更简单框架。

答案 5 :(得分:7)

我一直使用模板元编程,但在D中,不是C ++。 C ++的模板元语言最初是为简单类型参数化而设计的,并且几乎是偶然的,变成了图灵完整的元语言。因此,只有安德烈·亚历山大瑞斯(Andrei Alexandrescu),而不仅仅是凡人,才能使用图灵的图腾。

另一方面,D的模板子语言实际上是为简单类型参数化之外的元编程而设计的。 Andrei Alexandrescu seems to love it,但其他人实际上可以理解他的D模板。它也足够强大,有人在其中写了compile-time raytracer作为概念证明。

我想我在D中写过的最有用/非平凡的元程序是一个函数模板,给定一个struct类型作为模板参数,列表中的列标题名称对应于struct中的变量声明作为运行时参数,将读取CSV文件,并返回结构数组,每行一个结构,每个结构字段对应一列。所有类型转换(字符串到float,int等)都是根据模板字段的类型自动完成的。

另一个很好用的,但仍然不能正确处理一些情况,是一个深度复制函数模板,可以正确处理结构,类和数组。它只使用编译时反射/内省,因此它可以使用结构体,与完全成熟的类不同,结构体在D中没有运行时反射/内省功能,因为它们应该是轻量级的。

答案 6 :(得分:6)

大多数使用模板元编程的程序员通过boost等库来间接使用它。他们甚至不知道幕后发生了什么,只是它使得某些操作的语法更加容易。

答案 7 :(得分:4)

我在DSP代码中使用了很多,特别是FFT,固定大小的循环缓冲区,hadamard变换等。

答案 8 :(得分:4)

对于那些熟悉Oracle模板库(OTL),boost :: any和Loki库(现代C ++设计中描述的库)的人来说,这里是概念验证TMP代码,可以让你存储vector<boost::any>容器中的一行otl_stream,按列号访问数据。并且'是',我将把它合并到生产代码中。

#include <iostream>
#include <vector>
#include <string>
#include <Loki/Typelist.h>
#include <Loki/TypeTraits.h>
#include <Loki/TypeManip.h>
#include <boost/any.hpp>
#define OTL_ORA10G_R2
#define OTL_ORA_UTF8
#include <otlv4.h>

using namespace Loki;

/* Auxiliary structs */
template <int T1, int T2>
struct IsIntTemplateEqualsTo{
    static const int value = ( T1 == T2 );
};

template <int T1>
struct ZeroIntTemplateWorkaround{
    static const int value = ( 0 == T1? 1 : T1 );
};


/* Wrapper class for data row */
template <class TList>
class T_DataRow;


template <>
class T_DataRow<NullType>{
protected:
    std::vector<boost::any> _data;
public:
    void Populate( otl_stream& ){};
};


/* Note the inheritance trick that enables to traverse Typelist */
template <class T, class U>
class T_DataRow< Typelist<T, U> >:public T_DataRow<U>{
public:
    void Populate( otl_stream& aInputStream ){
        T value;
        aInputStream >> value;
        boost::any anyValue = value;
        _data.push_back( anyValue );

        T_DataRow<U>::Populate( aInputStream );
    }

    template <int TIdx>
    /* return type */
    Select<
        IsIntTemplateEqualsTo<TIdx, 0>::value,
        typename T,
        typename TL::TypeAt<
            U,
            ZeroIntTemplateWorkaround<TIdx>::value - 1
        >::Result
    >::Result
    /* sig */
    GetValue(){
    /* body */
        return boost::any_cast<
            Select<
                IsIntTemplateEqualsTo<TIdx, 0>::value,
                typename T,
                typename TL::TypeAt<
                    U,
                    ZeroIntTemplateWorkaround<TIdx>::value - 1
                >::Result
            >::Result
        >( _data[ TIdx ] );
    }
};


int main(int argc, char* argv[])
{
    db.rlogon( "AMONRAWMS/WMS@amohpadb.world" ); // connect to Oracle
    std::cout<<"Connected to oracle DB"<<std::endl;
    otl_stream o( 1, "select * from blockstatuslist", db );

    T_DataRow< TYPELIST_3( int, int, std::string )> c;
    c.Populate( o );
    typedef enum{ rcnum, id, name } e_fields; 
    /* After declaring enum you can actually acess columns by name */
    std::cout << c.GetValue<rcnum>() << std::endl;
    std::cout << c.GetValue<id>() << std::endl;
    std::cout << c.GetValue<name>() << std::endl;
    return 0;
};

对于那些不熟悉提到的图书馆的人。

OTL的otl_stream容器存在的问题是,只能按顺序访问列数据,方法是声明适当类型的变量,并按以下方式将operator >>应用于otl_stream对象:

otl_stream o( 1, "select * from blockstatuslist", db );
int rcnum; 
int id;
std::string name;
o >> rcnum >> id >> name; 

这并不总是方便。解决方法是编写一些包装类并使用otl_stream中的数据填充它。希望能够声明列类型列表,然后:

  • 采用列的类型T
  • 声明该类型的变量
  • 申请olt_stream::operator >>(T&)
  • 存储结果(在boost :: any的向量中)
  • 获取下一列的类型并重复,直到处理完所有列

你可以在Loki的Typelist结构,模板专业化和继承的帮助下完成所有这些。

在Loki的库构造的帮助下,您还可以生成一堆GetValue函数,这些函数返回适当类型的值,从列的编号中推导出它(实际上Typelist中的类型数)。

答案 9 :(得分:2)

不,我没有在生产代码中使用它。

为什么?

  1. 我们必须使用原生 平台编译器支持6个以上的平台。它的 很难在这种环境中使用STL,更不用说现代模板了 技术。
  2. 开发人员似乎不再继续保持C ++的进步。我们使用C ++ 我们必须的时候。我们拥有传统设计的遗留代码。新代码是 用其他东西完成,例如Java,Javascript,Flash。

答案 10 :(得分:2)

在询问这个问题后近8个月我终于使用了一些TMP,我使用TypeList接口来实现基类中的QueryInterface。

答案 11 :(得分:2)

我将它与boost :: statechart一起用于大型状态机。

答案 12 :(得分:1)

是的,当我在更现代的C ++界面中包装遗留API时,我主要做一些类似鸭子打字的事情。

答案 13 :(得分:-1)

不要那样做。其背后的原因如下:从模板元编程的本质来看,如果逻辑的某些部分是在编译时完成的,那么它所依赖的每个逻辑也必须在编译时完成。一旦启动它,在编译时执行逻辑的一部分,就没有返回。雪球将继续滚动,没有办法阻止它。

例如,您无法迭代boost :: tuple&lt;&gt;的元素,因为您只能在编译时访问它们。你必须使用模板元编程来实现简单直接的C ++,并且当C ++的用户不小心不要将太多东西转移到编译时,就会发生总是。有时很难看出编译时逻辑的某些使用何时会成为问题,有时程序员会急于尝试测试他们在Alexandrescu中读到的内容。无论如何,我认为这是一个非常糟糕的主意。

答案 14 :(得分:-4)

许多程序员不使用模板,因为直到最近编译器支持都很差。然而,尽管模板在pas中存在很多问题,但新的编译器有更好的支持。我编写的代码必须与Mac和Linux以及Microsoft Visual C ++上的GCC一起使用,并且只有GCC 4和VC ++ 2005这些编译器才能很好地支持该标准。

通过模板进行通用编程并不是您一直需要的,但绝对是您工具箱中的有用代码。

明显的示例容器类,但模板对许多其他东西也很有用。我自己工作的两个例子是:

  • 智能指针(例如参考计数,写时复制等)
  • 数学支持类,如矩阵,向量,样条曲线等,需要支持各种数据类型并且仍然有效。