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

时间:2013-10-23 07:20:09

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

我有一堆有名字的类型。 (它们有更多功能,但为了讨论起见,只有名称是相关的。)这些类型及其名称是在编译时使用宏设置的:

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

然后将类型组合在编译时列表(经典的简单递归编译时列表)中,我需要通过连接其对象的名称来创建列表的名称:

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

代码在这里被归结为可能包含错误的程度,但实际上这很有效。

除了它在运行时创建然后复制相当长的字符串,这些字符串表示在编译时实际上是众所周知的类型。由于这是一个对嵌入式设备运行的性能相当敏感的代码,我想改变它以便

  1. 列表的字符串理想情况下是在编译时创建的,或者,如果没有办法在运行时创建,那么
  2. 我只需要复制指向C字符串的指针,因为根据#1,字符串在内存中是固定的。
  3. 这是用C ++ 03编译的,我们现在一直坚持使用它。
  4. 我该怎么做?

    (如果这扩大了可用于此的脏技巧:只有foo对象的名称才能被代码创建和读取,并且只有foo_list名称字符串应该是人类可读。)

5 个答案:

答案 0 :(得分:4)

你可能想看一下boost mpl::string。一旦我的咖啡被踢进来就可以效仿......

编辑:咖啡已经踢了......:)

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

我确信你能够胜任上述情况以适应你的情况。注意:您必须忽略多字符常量警告...

更多警告:传递给mpl::string的多字符常量不能超过4个字符,因此,有些字必须如何分块(或由单个字符构成),所以长字符串可能是,mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'>如果无法做到,那么以上内容将无效..:/

答案 1 :(得分:4)

我想出了以下解决方案:

类型生成为:

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

这里的关键点是我们在编译时知道其名称的长度。这允许我们计算类型列表中名称的总长度:

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

知道编译时的总长度,我们可以分配适当大小的静态缓冲区来连接字符串 - 所以不会有任何动态分配:

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

该缓冲区应该在运行时填充,但只能填充一次,并且该操作非常便宜(比先前使用动态分配字符串及其在递归中连接的解决方案快得多):

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

以下是完整代码:

Live Demo on 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; 
}

输出是:

foobarbaz

P.S。你如何使用串联字符串?也许我们根本不需要生成连接字符串,从而减少了数据空间需求。

例如,如果您只需要打印字符串 - 那么

template<typename list>
void print();

就足够了。但缺点是,虽然数据量减小 - 这可能会导致代码大小增加。

答案 2 :(得分:3)

  1. 您可以创建字符串static,并且只需在运行时构建字符串一次,并且只在需要时才构建字符串。
  2. 然后返回它们的const引用,以便不会有任何不必要的复制。
  3. 示例:

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

    可能不是你要写的确切代码,但我认为这给你的意义。此外,您可以通过const char*返回names.c_str()

答案 3 :(得分:2)

您可以考虑使用外部构建步骤而不是语言内解决方案。例如,您可以编写基于Clang的工具来解析相关文件,并在另一个TU中自动创建T::name实现。然后将其集成到您的构建脚本中。

答案 4 :(得分:0)

如果我们可以假设您唯一的要求是实际流式传输类的名称 - 这意味着您不需要在其他地方整体使用连接字符串 - 您可以简单地推迟流式传输,但仍然可以从meta-获益编程(正如Evgeny已经指出的那样)。

虽然这个解决方案并不能满足您的要求#1(一个串联字符串),但我仍然想为其他读者指出解决方案。

不是通过编译时的类型列表,而是想要从所有T::name()函数构建一个地址序列,并在需要时将其传递给流函数。这是可能的,因为具有外部链接的变量可以用作模板非类型参数。当然,您的里程数可能因数据和代码大小而异,但除非您处于高性能环境中,否则我希望此方法至少同样适合,因为在运行时不必创建其他字符串。

请注意,我故意使用了可变参数模板(在C ++ 03中不可用),因为它更易读,更容易推理。

&#34;小提琴&#34;可用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;
}

现在使用C ++ 14,人们可能会使用像hana这样强大的库来编写解决方案,请参阅此hana fiddle