Как создавать статические строки из типов во время компиляции

У меня есть группа типов, у которых есть имя. (У них больше возможностей, но для этого обсуждения важно только имя.) Эти типы и их имена настраиваются во время компиляции с помощью макроса:

#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.