【问题标题】:How to create static strings from types at compile time如何在编译时从类型创建静态字符串
【发布时间】:2013-10-23 07:20:09
【问题描述】:

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

#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 编译,我们现在坚持使用它。

我该怎么做?

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

【问题讨论】:

  • 必须是字符串吗?你能用typeid吗?
  • @Adam:这会在文件中产生人类可读的字符串。
  • 可以加长度功能吗?
  • @ForEveR:鉴于foo 模板中的字符串在编译时是已知的,因此即使添加字符串长度的编译时常量也相当简单。为什么要问?

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


【解决方案1】:

你可能想看看 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 个字符,因此,它必须如何分块(或由单个字符构成),所以一个长字符串可能是,@ 987654324@如果不能这样做,那么上面的就行不通了..:/

【讨论】:

  • 好的,这看起来很有趣。我有一个问题:由于这是一个专有的嵌入式平台,带有定制(旧)GCC,这严格来说是 C++03。(对不起,我忘了在问题中说这个. 我现在添加它。)其中,变量参数模板对我们来说是不可能使用的。另一个问题是我从来没有考虑过像'foo' 这样过长的字符常量。那是什么类型的?那可以是任意长度吗?如果没有,那么这对我们不起作用。
  • 我在使用可变参数模板参数时很懒惰,mpl::vector&lt;&gt; 也可以在 03 中使用,您只需要显式列出所有类型即可。至于多字符常量,我认为它们默认为int,这就是每个常量只允许四个字符的原因。就像我在警告中所说的那样,您需要能够将类型名称的长度限制为四个字符,或者以某种方式将它们组合成四个字符的块。
【解决方案2】:

我想出了以下解决方案:

类型生成为:

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

附:你如何使用连接字符串?也许我们根本不需要生成连接字符串,减少数据空间需求。

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

template<typename list>
void print();

就足够了。但不利的一面是,在减少数据大小的同时 - 这可能会导致代码大小增加。

【讨论】:

  • 好吧,如果我们打印字符串,那么它连接的,不是吗? :) 确实需要打印字符串(我说它们应该是人类可读的),但是编译时列表位于间隙的一侧,而打印位于另一侧,因此他们必须被渲染以便跨越那个间隙。
  • 无论如何,这似乎是一个很好的解决方案 (+1)。但是,它比Mark Garcia's answer 有什么优势呢?和你的一样,他的解决方案也在运行时构建了一次字符串,而且他的解决方案没有那么复杂。
  • @sbi 不同之处在于,对于他的解决方案,您必须执行 O(n) dynamic allocations 以在每个类型列表中连接 n stings .在我的解决方案中,您只需为每个类型列表执行 O(1) 静态分配,并使用已知的编译时大小(成本要低得多)。
【解决方案3】:
  1. 您可以创建字符串static,并且只需在运行时构造一次字符串,并且仅在需要时构造。
  2. 然后返回它们的 const 引用,这样就不会有任何不必要的复制。

例子:

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

可能不是您要编写的确切代码,但我认为这给了您重点。此外,您可以通过执行names.c_str() 返回const char*

【讨论】:

  • 嗯。这会在运行时构建字符串,尽管只是在第一次。而且,是的,names.c_str() 返回的地址应该是固定的。让我们看看是否有人找到了纯编译时解决方案,否则我可能会这样做。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

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

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

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

    “小提琴”可用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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-21
      • 2015-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-16
      • 2021-12-13
      • 2012-08-03
      相关资源
      最近更新 更多