Как создавать статические строки из типов во время компиляции
У меня есть группа типов, у которых есть имя. (У них больше возможностей, но для этого обсуждения важно только имя.) Эти типы и их имена настраиваются во время компиляции с помощью макроса:
#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, строки фиксируются в памяти.
- Это компиляция с С++ 03, с которой мы застряли прямо сейчас.
Как я могу это сделать?
(В случае, если это расширит арсенал грязных трюков, которые можно использовать для этого: Имена объектов foo
создаются и считываются только кодом, и ожидается, что строки имен foo_list
будут доступны для человека. )
Ответы
Ответ 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 символов, поэтому некоторые из них должны быть разбиты на отдельные (или построены из отдельных символов), поэтому может быть длинной строки mpl::string<'this', ' is ', 'a lo', 'ng s', 'trin', 'g'>
Если это невозможно, то указанное выше не будет работать.:/
Ответ 2
Я придумал следующее решение:
Тип создается как:
const char foo_str [] = "foo";
struct X
{
static const char *name() { return foo_str; }
enum{ name_size = sizeof(foo_str) };
};
Keypoint - это то, что мы знаем длину его имени во время компиляции. Это позволяет вычислить общую длину имен в списке типов:
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();
будет достаточно. Но недостатком является то, что при уменьшении размера данных, что может привести к увеличению размера кода.
Ответ 3
- Вы можете сделать строку
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()
.
Ответ 4
Вы можете использовать внешний шаг сборки вместо решения на языке. Например, вы можете написать инструмент на основе Clang для анализа соответствующих файлов и автоматического создания реализаций T::name
в другом TU. Затем интегрируйте его в свою сборку script.
Ответ 5
Если бы мы могли предположить, что ваше единственное требование состоит в том, чтобы фактически передавать имена классов, то есть вам не нужны конкатенированные строки в других местах в целом - вы могли бы просто отложить потоковое воспроизведение, но все же выиграть от метапрограммирования ( как уже указывал Евгений).
Хотя это решение не удовлетворяет вашему требованию № 1 (одна конкатенированная строка), я бы все же хотел указать на решение для других читателей.
Вместо того, чтобы переходить к списку типов времени компиляции, идея состоит в том, чтобы построить последовательность адресов из всех функций T::name()
и передать их в поточную функцию, когда это необходимо. Это возможно, потому что переменные с внешней связью могут использоваться как аргументы шаблона, не относящиеся к типу. Разумеется, ваш пробег может варьироваться в зависимости от данных и размера кода, но, если вы не находитесь в высокопроизводительной среде, я ожидаю, что такой подход будет по меньшей мере одинаково подходящим, поскольку во время выполнения никаких дополнительных строк не требуется.
Обратите внимание, что я намеренно использовал вариативные шаблоны (недоступно в С++ 03), потому что он гораздо читабельнее и проще рассуждать.
"Fiddle" доступен здесь.
#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;
}
В наши дни с С++ 14 можно было бы написать решение с помощью мощной библиотеки, такой как hana, см. эту hana fiddle.