如何优雅地编写使用模板参数隐含的值的类模板?

时间:2016-10-13 17:50:24

标签: c++ templates

我正在编写一个源文件库,其中包含一系列与一对一关系相关的声明和宏。第一个是类别列表,作为枚举:

typedef enum {
    CID_SYS,                // Highest-priority devices. Reserved.
    CID_CTRL,               // Controlling unit, only one per segment
    CID_SENSOR,             // Data providers (temperature, speed, clock)
    CID_BROADCAST,          // Broadcast messages (system messages extension)
    ...
} category_id_t;

我使用此枚举来定义16位消息标识符,类别位为最高3位。这些标识符在较低有效位中被分成两个可变大小的位块。其中一个块取决于上述类别。所以我还将大小列表定义为宏,每个类别一个,如下所示:

#define SYS_MESSAGES_MAX        256
#define CTRL_MESSAGES_MAX       64
#define SENSOR_MESSAGES_MAX     8
#define BROADCAST_MESSAGES_MAX  64
...
然后,很容易屏蔽该类别并检索相关位,即功能ID,它位于消息ID的最低有效位中。以CID_SYS为例:

unsigned short function_id = message_id & (SYS_MESSAGES_MAX-1)

我需要一个类别作为参数的类模板。后者隐含的类别中的消息数应该以某种方式在编译时由模板类推导而不依赖于数组。类模板看起来可能类似:

template <category_id_t CAT>
class Manager
{
    ...
    unsigned message_count() const { return /* a number depending on CAT */ }
};

使用-Os,编译器可以在编译时尽可能地解析,而不会在可能的情况下添加代码或变量。所以我想充分利用它。我目前的尝试是使用功能模板和专业化:

template<category_id_t CAT>
unsigned cat_size();

template<category_id_t CAT>
class Manager
{
public:
        unsigned size() const { return cat_size<CAT>(); }
};

template<> unsigned cat_size<CID_SYS>() { return SYS_MESSAGES_MAX; }
template<> unsigned cat_size<CID_CTRL>() { return CTRL_MESSAGES_MAX; }

上面的例子将是:

unsigned short function_id = message_id & (size()-1) /* This should be a constant resolved at compile-time */

如果我在添加类别时忘记了特殊化,那么​​通常会在没有定义的情况下保留通用模板函数,以便生成链接器错误。但是我发现这种不雅和错综复杂。

我怎么能让这更优雅?

我绝对不想将消息计数作为模板参数传递,因为我仍然需要C风格的宏:我的库应该由C C ++应用程序使用。

2 个答案:

答案 0 :(得分:1)

这可能不是很整洁或优雅,但我有一些搜索元函数在类型列表中查找类型如下:

#include <type_traits>

template<typename ...Ts>
struct TypeList; //Represent a list of types to be queried

struct Nil; //empty type, a placeholder type if we cannot find what we need

//Searches given 'Item' in types ('Ts...') where equality check is done by 'Equals'
template<typename Item, template<class,class> class Equals, typename ...Ts>
struct Find;

//Specializes the 'Find' with 'TypeList' provides syntactic sugar
template<typename Item, template<class,class> class Equals, typename ...Ts>
struct Find<Item, Equals, TypeList<Ts...>> : Find<Item, Equals, Ts...> 
{};

//recursive 'Find' metafunction. If 'T' is equal to 'Item' then return 'T'
//                               Else recurse to the rest of the type list
template<typename Item, template<class,class> class Equals, typename T, typename ...Ts>
struct Find<Item, Equals, T, Ts...> {
    using type = typename std::conditional<
        Equals<Item, T>::value, //Evaluate T
        T, //if predicate returns true than T is the type we are looking for
        Find<Item, Equals, Ts...> //else recurse into the list
    >::type;
};

//specialization for one type 'T', that is the last element of the original type-list
template<typename Item, template<class,class> class Equals, typename T>
struct Find<Item, Equals, T> {
    using type = typename std::conditional<
        Equals<Item, T>::value, //Evaluate T
        T, //if predicate returns true than T is the type we are looking for
        Nil //else return Nil for meaningful compile messages
    >::type;
};

您可以在实用程序标头中使用它并将其用于各种目的。 Boost有两个不同的类用于这些类(元编程),其中一个是MPL,另一个现代版本是Hana。您可能想要检查其中一个库。

通过这种类型搜索机制,我们可以为您的类别定义类型并保存类别相关信息。

//A special structure to define your compile-time attributes for each category
template<category_id_t CatId, int CatMask>
struct Category
{
    static const category_id_t id = CatId;
    static const int mask = CatMask;
};

//define a set of categories with their attributes (ie. id and mask)
using Categories = TypeList<
    Category<CID_SYS, SYS_MESSAGES_MAX-1>,
    Category<CID_CTRL, CTRL_MESSAGES_MAX-1>,
    Category<CID_SENSOR, SENSOR_MESSAGES_MAX-1>,
    Category<CID_BROADCAST, BROADCAST_MESSAGES_MAX-1>
>;

然后我们定义一个谓词和一个专门的搜索函数来查找给定id的相关类别,如下所示:

//
template<typename Item, typename Category_>
using CategoryEquals = std::integral_constant<
    bool,
    Item::value == Category_::id
>;

template<category_id_t CatId>
using FindCategory = Find<
    std::integral_constant<category_id_t, CatId>, //Item
    CategoryEquals, //Equals
    Categories
>;

最后,我们可以找到并使用这样的类别:

template<category_id_t CatId>
unsigned short GetFunctionId(unsigned short messageId)
{
    using FoundCat = typename FindCategory<CatId>::type; //search for category

    return messageId & FoundCat::mask;
}

样本用法:

int main()
{
    unsigned short msg = 259;
    unsigned short functionId = GetFunctionId<CID_SYS>(msg);

    std::cout << functionId; //prints 3
}

答案 1 :(得分:1)

现在我们有constexpr,这实际上是可行的,没有模板。 cat_size映射可以使用带有constexpr的{​​{1}}函数完成。如果您不希望在返回值中包含值,则可以将switch定义为单独的*_MESSAGES_MAX

constexpr int

计算函数id只是调用交换机的另一个constexpr int cat_size(category_id_t cat) { switch (cat) { case CID_SYS: return 256; // SYS_MESSAGES_MAX case CID_CTRL: return 64; // CTRL_MESSAGES_MAX case CID_SENSOR: return 8; // SENSOR_MESSAGES_MAX case CID_BROADCAST: return 64; // BROADCAST_MESSAGES_MAX } } 。我已将constexpr替换为unsigned short以确保您获得所需内容,请注意这需要您std::uint16_t

#include <cstdint>

查看以下生成的程序集,我们可以看到它实际上是在编译时计算的

constexpr std::uint16_t function_id(category_id_t cat, std::uint16_t msg) {
  return msg & (cat_size(cat)-1);
}