简体   繁体   English

如何在编译时从类型创建静态字符串

[英]How to create static strings from types at compile time

I have a bunch of types that have a name. 我有一堆有名字的类型。 (They have more features, but for the sake of this discussion only the name is relevant.) These types and their names are setup at compile-time using a macro: (它们有更多功能,但为了讨论起见,只有名称是相关的。)这些类型及其名称是在编译时使用宏设置的:

#define DEFINE_FOO(Foo_)                        \
    struct Foo_ : public foo_base<Foo_> {       \
      static char const* name() {return #Foo_;} \
    }

The types are then combined in compile-time lists (classic simple recursive compile-time lists), from which I need to create the list's name by concatenating the names of its objects: 然后将这些类型组合在编译时列表(经典的简单递归编译时列表)中,我需要通过连接其对象的名称来创建列表的名称:

template<class Foo, class Tail = nil>
struct foo_list {
  static std::string name_list() {return Foo::name() + "-" + Tail::name();}
};
template<class Foo>
struct foo_list<Foo,nil> {
  static std::string name_list() {return Foo::name();}
};

The code is boiled down here to the point where it might contain errors, but in practice this works pretty well. 代码在这里归结为它可能包含错误的程度,但实际上这很好用。

Except that it creates and then copies around rather long strings at runtime which represent types that actually are well-known at compile-time. 除了它在运行时创建然后复制相当长的字符串,这些字符串表示在编译时实际上是众所周知的类型。 Since this is a rather performance-sensitive piece of code that runs on embedded devices, I'd like to change this so that 由于这是一个在嵌入式设备上运行的性能相当敏感的代码,我想改变这一点

  1. the list's string is ideally created at compile-time, or, if there's no way to do that, once at runtime, and 列表的字符串理想地是在编译时创建的,或者,如果没有办法在运行时创建它,并且
  2. I only need to copy around a pointer to a C string, since, according to #1, the strings are fixed in memory. 我只需要复制一个指向C字符串的指针,因为根据#1,字符串在内存中是固定的。
  3. This compiles with C++03, which we're stuck with right now. 这与C ++ 03一起编译,我们现在一直坚持使用它。

How can I do this? 我怎样才能做到这一点?

(In case this enlarges the arsenal of dirty tricks employable for this: The names of the foo objects are only ever created and read by code, and only the foo_list name strings are expected to be human-readable.) (如果这扩大了可用于此的脏技巧: foo对象的名称只能由代码创建和读取,并且只有foo_list名称字符串应该是人类可读的。)

You probably want to look at boost's mpl::string . 你可能想看看boost的mpl::string Example to follow once my coffee has kicked in... 一旦我的咖啡被踢进去的例子......

EDIT: So coffee has kicked in... :) 编辑:所以咖啡踢了...... :)

#include <iostream>

#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/vector.hpp>

namespace mpl = boost::mpl;

struct foo
{
  typedef mpl::string<'foo'> name;
};

struct bar
{
  typedef mpl::string<'bar'> name;
};

struct gah
{
  typedef mpl::string<'gah'> name;
};

namespace aux
{

template <typename string_type, typename It, typename End>
struct name_concat
{
  typedef typename mpl::insert_range<string_type, typename mpl::end<string_type>::type, typename mpl::deref<It>::type::name>::type base;
  typedef typename aux::name_concat<base, typename mpl::next<It>::type, End>::name name;
};

template <typename string_type, typename End>
struct name_concat<string_type, End, End>
{
  typedef string_type name;
};

}

template <typename ...Types>
struct type_list
{
  typedef mpl::string<> base;
  typedef mpl::vector<Types...> type_seq;
  typedef typename aux::name_concat<base, typename mpl::begin<type_seq>::type, typename mpl::end<type_seq>::type>::name name;
};

int main(void)
{
  typedef typename type_list<foo, bar, gah>::name tlist_name;
  std::cout << mpl::c_str<tlist_name>::value << std::endl;
}

I'm sure you are more than competent enough to tweak the above for your situation. 我相信你有足够的能力来根据你的情况调整上述内容。 NOTE: you will have to ignore the multi-character constant warnings... 注意:您将不得不忽略多字符常量警告...

Couple of more caveats: the multi-character constant passed to mpl::string cannot be more than 4 characters, so, some how it has to be chunked (or constructed of individual characters), so a long string could be, mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'> If this is cannot be done, then the above will not work.. :/ 几个警告:传递给mpl::string的多字符常量不能超过4个字符,因此,有些如何分块(或由单个字符构成),所以长字符串可以是, mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'>如果这是无法完成的,那么上面的代码将不起作用..:/

I came up with following solution: 我想出了以下解决方案:

Type is generated as: 类型生成为:

const char foo_str [] = "foo";
struct X
{
    static const char *name() { return foo_str; }
    enum{ name_size = sizeof(foo_str) };
};

Keypoint here is that we know length of its name at compile time. 这里的关键点是我们在编译时知道其名称的长度。 That allow us to calculate total length of names in typelist: 这允许我们计算类型列表中名称的总长度:

template<typename list>
struct sum_size
{
    enum
    {
       value = list::head::name_size - 1 +
               sum_size<typename list::tail>::value
    };
};
template<>
struct sum_size<nil>
{
    enum { value = 0 };
};

Knowing total length at compile time, we can allocate static buffer of appropriate size for concatenation of strings - so there will be no any dynamic allocations: 知道编译时的总长度,我们可以分配适当大小的静态缓冲区来连接字符串 - 所以不会有任何动态分配:

static char result[sum_size<list>::value + 1];

That buffer should be filled at runtime, but only once, and that operation is pretty cheap (much faster than previous solution with dynamic allocation of strings and their concatenation in recursion): 该缓冲区应该在运行时填充,但只能填充一次,并且该操作非常便宜(比以前的解决方案快得多,动态分配字符串及其在递归中的连接):

template<typename list>
const char *concate_names()
{
    static char result[sum_size<list>::value + 1];
    static bool calculated = false;
    if(!calculated)
    {
        fill_string<list>::call(result);
        calculated = true;
    }
    return result;
}

Here is full code: 这是完整的代码:

Live Demo on Coliru Coliru现场演示

#include <algorithm>
#include <iostream>
using namespace std;

/****************************************************/

#define TYPE(X) \
const char X ## _str [] = #X; \
struct X \
{ \
    static const char *name() { return X ## _str; }  \
    enum{ name_size = sizeof(X ## _str) }; \
}; \
/**/

/****************************************************/

struct nil {};

template<typename Head, typename Tail = nil>
struct List
{
    typedef Head head;
    typedef Tail tail;
};

/****************************************************/

template<typename list>
struct sum_size
{
    enum { value = list::head::name_size - 1 + sum_size<typename list::tail>::value };
};
template<>
struct sum_size<nil>
{
    enum { value = 0 };
};

/****************************************************/

template<typename list>
struct fill_string
{
    static void call(char *out)
    {
        typedef typename list::head current;
        const char *first = current::name();
        fill_string<typename list::tail>::call
        (
            copy(first, first + current::name_size - 1, out)
        );
    }
};

template<>
struct fill_string<nil>
{
    static void call(char *out)
    {
        *out = 0;
    }
};

/****************************************************/

template<typename list>
const char *concate_names()
{
    static char result[sum_size<list>::value + 1];
    static bool calculated = false;
    if(!calculated)
    {
        fill_string<list>::call(result);
        calculated = true;
    }
    return result;
}

/****************************************************/

TYPE(foo)
TYPE(bar)
TYPE(baz)

typedef List<foo, List<bar, List<baz> > > foo_list;

int main()
{
    cout << concate_names<foo_list>() << endl; 
}

Output is: 输出是:

foobarbaz

PS How do you use concatenated string? PS你如何使用串联字符串? Maybe we don't need to generate concatenated string at all, reducing data space requirement. 也许我们根本不需要生成连接字符串,从而减少了数据空间需求。

For example if you just need to print string - then 例如,如果你只需要打印字符串 - 那么

template<typename list>
void print();

will be sufficient. 就足够了。 But downside is that while decreasing data size - that could lead to increased size of code. 但缺点是,虽然数据量减小 - 这可能会导致代码量增加。

  1. You could make the string static and you only have to construct the string once at runtime and only when it is needed. 您可以将字符串设置为static并且只需在运行时和仅在需要时构造字符串一次。
  2. Then you return a const reference of them so that there wouldn't be any unnecessary copying. 然后返回它们的const引用,这样就不会有任何不必要的复制。

Example: 例:

template<class Foo, class Tail = nil>
struct foo_list {
  static const std::string& name_list() {
     static std::string names = Foo::name() + std::string("-") + Tail::name();
     return names;
  }
};

template<class Foo>
struct foo_list<Foo,nil> {
  static const std::string& name_list() {
     static std::string names = Foo::name();
     return names;
  }
};

Might not be the exact code that you'll write, but I think that gives you the point. 可能不是你要写的确切代码,但我认为这给你的意义。 Also, you could return a const char* by doing names.c_str() . 此外,您可以通过执行names.c_str()返回const char*

You can consider using an external build step instead of an in-language solution. 您可以考虑使用外部构建步骤而不是语言内解决方案。 For example, you could write a tool based on Clang to parse the relevant files and create the T::name implementations automatically in another TU. 例如,您可以编写基于Clang的工具来解析相关文件,并在另一个TU中自动创建T::name实现。 Then integrate it into your build script. 然后将其集成到您的构建脚本中。

If we could assume that your only requirement is to actually stream the names of the classes - meaning you don't need the concatenated strings at other places as a whole - you could simply defer streaming but still benefit from meta-programming (as Evgeny already pointed out). 如果我们可以假设您唯一的要求是实际流式传输类的名称 - 这意味着您不需要整个其他位置的连接字符串 - 您可以简单地推迟流式传输但仍然可以从元编程中受益(因为Evgeny已经指出)。

While this solution doesn't satisfy your requirement #1 (one concatenated string), I'd still like to point out a solution for other readers. 虽然这个解决方案不能满足您的要求#1(一个串联字符串),但我仍然想为其他读者指出一个解决方案。

Instead of going through a compile-time list of types, the idea is to build a sequence of addresses from all T::name() functions and pass that into a streaming function when needed. 这个想法是从所有T::name()函数构建一个地址序列,并在需要时将其传递给流函数,而不是通过编译时的类型列表。 This is possible because variables with external linkage can be used as template non-type arguments. 这是可能的,因为具有外部链接的变量可以用作模板非类型参数。 Your mileage may vary in terms of data and code size of course, but unless you are in a high-performance environment I expect this approach to be at least equally suitable as no additional strings have to be created at runtime. 当然,您的里程数可能会因数据和代码大小而异,但除非您处于高性能环境中,否则我希望此方法至少同样适合,因为在运行时不必创建其他字符串。

Note that I deliberately used variadic templates (not available in C++03) because it's much more readable and simpler to reason about. 请注意,我故意使用可变参数模板(在C ++ 03中不可用),因为它更易读,更容易推理。

"Fiddle" available here . 这里有 “小提琴”。

#include <ostream>
#include <boost/core/ref.hpp>
#include <boost/bind.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/for_each.hpp>

namespace mpl = boost::mpl;


template<typename>
class foo_base
{};

#define DECLARE_FOO(Foo_) \
    struct Foo_ : public foo_base<Foo_> { \
        static char const* name() {return #Foo_;} \
    };


// our own integral constant because mpl::integral_c would have to be specialized anyway
template<typename T, T Value>
struct simple_integral_c
{
    operator T() const { return Value; }
};

template<typename T, T ...Values>
struct ic_tuple : mpl::vector<simple_integral_c<T, Values>...>
{};


typedef const char*(*NameFunction)();

template <NameFunction ...Functions>
struct function_list : ic_tuple<NameFunction, Functions...>
{};

template <typename ...Types>
struct function_of_list : function_list<&Types::name...>
{};


struct print_type
{
    void operator ()(std::ostream& os, NameFunction name)
    {
        if (nth++)
            os << "-";
        os << name();
    }

    print_type(): nth(0) {}

private:
    int nth;
};

// streaming function
template<NameFunction ...Functions>
std::ostream& operator <<(std::ostream& os, function_list<Functions...>)
{
    mpl::for_each<function_list<Functions...>>(
        boost::bind<void>(print_type(), boost::ref(os), _1)
    );

    return os;
}

These days with C++14 one would probably write the solution with a powerful library like hana, see this hana fiddle . 现在用C ++ 14可能会用像hana这样强大的库编写解决方案,看看这个hana小提琴

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM