C++, я могу статически инициализировать std::map во время компиляции?

Если я кодирую это

std::map<int, char> example = {
                                (1, 'a'),
                                (2, 'b'),
                                (3, 'c') 
                              };

тогда g++ говорит мне

deducing from brace-enclosed initializer list requires #include <initializer_list>
in C++98 ‘example’ must be initialized by constructor, not by ‘{...}’   

и это немного раздражает меня, потому что конструктор во время выполнения и может, теоретически, потерпеть неудачу.

Конечно, если это произойдет, он быстро потерпит неудачу и должен делать это последовательно, так что я должен быстро найти и исправить проблему.

Но, тем не менее, мне любопытно - есть ли способ инициализировать карту, вектор и т. Д. Во время компиляции?


Изменить: я должен был сказать, что я разрабатываю для встроенных систем. Не все процессоры будут иметь компилятор C++0x. Наиболее популярным, вероятно, будет, но я не хочу сталкиваться с ошибкой и должен поддерживать 2 версии кода.

Что касается Boost, я не определился. Они неохотно используют свои классы конечных автоматов во встроенных системах, так что на самом деле это то, что я здесь кодирую, классы Event/State/Fsm.

Вздох, я думаю, мне лучше просто обойтись безопасным, но я надеюсь, что эта дискуссия была полезна для других.

9 ответов

Решение

Не в C++98. C++11 поддерживает это, поэтому, если вы включите флаги C++ 11 и включите то, что предлагает g++, вы можете.

Редактировать: из gcc 5 C++11 включен по умолчанию

Это не совсем статическая инициализация, но все же, попробуйте. Если ваш компилятор не поддерживает C++0x, я бы использовал конструктор итераций std::map:

std::pair<int, std::string> map_data[] = {
    std::make_pair(1, "a"),
    std::make_pair(2, "b"),
    std::make_pair(3, "c")
};

std::map<int, std::string> my_map(map_data,
    map_data + sizeof map_data / sizeof map_data[0]);

Это довольно читабельно, не требует дополнительных библиотек и должно работать во всех компиляторах.

Вы можете использовать библиотеку Boost.Assign:

#include <boost/assign.hpp>
#include <map>
int main()
{
   std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');
}

Однако, как указали Нейл и другие в комментариях ниже, эта инициализация происходит во время выполнения, аналогично предложению UncleBean.

С C++0x вам может понадобиться использовать фигурные скобки полностью (используйте синтаксис нового стиля для каждой пары):

std::map<int, char> example = { {1,'a'}, {2, 'b'}, {3, 'c'} };

Эти скобки для построения пар не имеют смысла. В качестве альтернативы вы можете полностью назвать каждую пару или использовать make_pair (как вы сделали бы в C++98)

std::map<int, char> example = {
    std::make_pair(1,'a'),
    std::make_pair(2, 'b'),
    std::make_pair(3, 'c')
};

Что касается создания этих экземпляров во время компиляции: нет. Все контейнеры STL полностью управляют памятью во время выполнения.

Полагаю, у вас действительно будет только карта времени компиляции с библиотеками, такими как метапрограммирование boost (не уверен на 100%, если она полностью верна, и не изучите, для чего она может быть полезна):

using namespace boost::mpl;
map<
    pair<integral_c<int, 1>, integral_c<char, 'a'> >,
    pair<integral_c<int, 2>, integral_c<char, 'b'> >,
    pair<integral_c<int, 3>, integral_c<char, 'c'> >
> compile_time_map;

С pre-C++0x самое близкое, что вы можете получить, это не использовать контейнеры, предназначенные для использования во время выполнения (и ограничивать себя фундаментальными типами и агрегатами):

struct pair { int first; char second; };
pair arr[] = {{1,'a'}, {2,'b'}}; // assuming arr has static storage

Затем к нему можно получить доступ, используя какой-либо вид карты, или вы можете реализовать оболочку, которая допускает агрегатную инициализацию, аналогичную той, что делает Boost.Array.

Конечно, вопрос заключается в том, насколько выгодны затраты времени на реализацию этого.

Если мое чтение верно, то C++0x initializer-lists может дать вам статическую инициализацию неагрегатов вроде std::map а также std::pair, но только если это не меняет семантику по сравнению с динамической инициализацией.
Таким образом, мне кажется, что вы можете получить то, о чем просили, только если ваша реализация может проверить с помощью статического анализа, что поведение не изменится, если map статически инициализируется, но не гарантирует, что это произойдет.

Есть хитрость, которую вы можете использовать, но только если эти данные не будут использоваться ни в каком другом статическом конструкторе. Сначала определите простой класс следующим образом:

typedef void (*VoidFunc)();
class Initializer
{
  public:
    Initializer(const VoidFunc& pF)
    {
      pF();
    }
};


Затем используйте это так:

std::map<std::string, int> numbers;
void __initNumsFunc()
{
  numbers["one"] = 1;
  numbers["two"] = 2;
  numbers["three"] = 3;
}
Initializer __initNums(&__initNumsFunc);


Конечно, это немного излишне, поэтому я бы рекомендовал использовать его только в том случае, если это действительно необходимо.

Нет стандартного способа инициализации std::map во время компиляции. Как уже упоминалось, C++0x позволит компилятору оптимизировать инициализацию, чтобы она была статической, если это возможно, но это никогда не будет гарантировано.

Помните, однако, что STL - это просто спецификация интерфейса. Вы можете создавать свои собственные совместимые контейнеры и предоставлять им возможность статической инициализации.

В зависимости от того, планируете ли вы обновить свой компилятор и реализацию STL (особенно на встроенной платформе), вы можете даже просто изучить используемую реализацию, добавить производные классы и использовать их!

Нет, потому что все компоненты карты, вектора и строк в вашем примере созданы с использованием памяти, которая выделяется во время выполнения через new. Использование std::initializer_list и других сахаров в конечном итоге просто выполняет работу конструктора, к которой мы привыкли, от нашего имени.

Чтобы это действительно было время компиляции, как в вашей структуре в obj, вам придется частично скомпилировать программу, запустить ее, сериализовать результаты инициализатора в obj, а затем вернуть процесс при загрузке. Проделать это с простыми массивами, строками и скалярами не так уж и плохо, но совсем другое дело — целый сложный набор структур.

Но придержите эту мысль о сериализации. Что вы могли бы сделать, так это сделать что-то вроде nlohmann json для сериализации/десериализации вашей структуры, а затем использовать строку json вашей структуры данных в качестве строковой константы. Эти данные будут вполне пригодны во время компиляции, а затем вы сможете загрузить их во время выполнения. Или вы можете просто использовать то, что у вас есть, собрать коллекцию известных конструкций времени компиляции и прочитать их во время выполнения. Например, вы можете представить свою карту в виде массива 2xN, а затем прочитать этот массив во время выполнения.

template <const int N> struct Map  { enum { value = N}; };
template <> struct Map <1> { enum { value = (int)'a'}; };
template <> struct Map <2> { enum { value = (int)'b'}; };
template <> struct Map <3> { enum { value = (int)'c'}; };

std::cout  << Map<1>::value ;
Другие вопросы по тегам