提问人:sbi 提问时间:10/23/2013 最后编辑:sbi 更新时间:1/5/2017 访问量:2184
如何在编译时从类型创建静态字符串
How to create static strings from types at compile time
问:
我有一堆有名字的类型。(它们具有更多功能,但出于讨论的目的,仅名称相关。这些类型及其名称是在编译时使用宏设置的:
#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();}
};
代码在这里被归结为可能包含错误的程度,但在实践中这非常有效。
除了它在运行时创建并复制相当长的字符串,这些字符串表示在编译时实际上是已知的类型。由于这是一段在嵌入式设备上运行的性能相当敏感的代码,因此我想对此进行更改,以便
- 列表的字符串最好在编译时创建,或者,如果没有办法这样做,则在运行时创建一次,并且
- 我只需要复制指向 C 字符串的指针,因为根据 #1,字符串在内存中是固定的。
- 这与我们现在坚持使用的 C++03 一起编译。
我该怎么做?
(如果这扩大了可用于此目的的肮脏伎俩的武器库:对象的名称只能通过代码创建和读取,并且只有名称字符串应该是人类可读的。foo
foo_list
答:
您可能想查看 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;
}
我相信您有足够的能力根据您的情况调整上述内容。注意:您将不得不忽略多字符常量警告...
还有几点需要注意:传递给的多字符常量不能超过 4 个字符,因此,它必须以某种方式分块(或由单个字符构造),因此长字符串可能是,如果无法做到这一点,那么上述方法将不起作用。:/mpl::string
mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'>
评论
'foo'
mpl::vector<>
int
- 您可以创建字符串,并且只需在运行时构造一次字符串,并且仅在需要时构造字符串。
static
- 然后返回它们的常量引用,这样就不会有任何不必要的复制。
例:
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()
评论
names.c_str()
可以考虑使用外部生成步骤,而不是语言内解决方案。例如,您可以编写一个基于 Clang 的工具来解析相关文件并在另一个 TU 中自动创建实现。然后将其集成到构建脚本中。T::name
我想出了以下解决方案:
类型生成如下:
const char foo_str [] = "foo";
struct X
{
static const char *name() { return foo_str; }
enum{ name_size = sizeof(foo_str) };
};
这里的关键点是我们在编译时知道其名称的长度。这允许我们计算 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 };
};
知道编译时的总长度,我们可以分配适当大小的静态缓冲区来连接字符串 - 这样就不会有任何动态分配:
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;
}
以下是完整代码:
#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();
就足够了。但缺点是,在减少数据大小的同时,这可能会导致代码大小增加。
评论
:)
+1
如果我们可以假设你唯一的要求是实际流式处理类的名称——这意味着你不需要其他地方的串联字符串作为一个整体——你可以简单地推迟流式处理,但仍然受益于元编程(正如 Evgeny 已经指出的那样)。
虽然此解决方案不能满足您的要求 #1(一个串联字符串),但我仍然想为其他读者指出一个解决方案。
这个想法不是通过编译时的类型列表,而是从所有函数构建一个地址序列,并在需要时将其传递到流式处理函数中。这是可能的,因为具有外部链接的变量可以用作模板非类型参数。当然,您的里程可能会在数据和代码大小方面有所不同,但除非您处于高性能环境中,否则我希望这种方法至少同样适用,因为在运行时无需创建额外的字符串。T::name()
请注意,我特意使用了可变参数模板(在 C++03 中不可用),因为它更具可读性且更易于推理。
“小提琴”可在此处获得。
#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 小提琴。
评论
typeid
foo