Инициализировать массив определенного времени компиляции как константное выражение - PullRequest
2 голосов
/ 06 июня 2019

У меня есть массив строк, которые должны быть выделены один раз, и их базовые значения c_str должны оставаться действительными в течение всей продолжительности программы.

Есть некоторый API, который предоставляет информацию о некоторых произвольных типах данных.Может выглядеть так:

// Defined outside my code
#define NUMBER_OF_TYPES 23
const char* getTypeSuffix(int index);

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

Интерфейс, который я должен Обеспечить:

// Returned pointer must statically allocated (not on stack, not malloc)
const char* getReadableTypeName(int type);

Теперь мой массив должен иметь следующий тип:

std::string typeNames[NUMBER_OF_TYPES];

Для моих целей он будет инициализирован внутри класса-оболочки прямо в конструкторе:

class MyNames
{
  MyNames()
  {
    for (int i = 0; i < NUMBER_OF_TYPES; ++i)
    {
      names[i] = std::string("Type ") + getTypeSuffix(i);
    }
  }

  const char* operator[](int type) { return _names[(int)type].c_str(); }

private:
  std::string _names[NUMBER_OF_TYPES];
};

Это затем используется в виде синглтона, например:

const char* getReadableTypeName(int type) 
{
  static MyNames names;
  return names[type];
}

Теперь я хочу улучшить то, что я вижу, что цикл for в конструкторе могможно заменить следующим образом:

 MyNames() : _names{std::string("Type ") + getTypeSuffix(0), std::string("Type ") + getTypeSuffix(1), ... , std::string("Type ") + getTypeSuffix(NUMBER_OF_TYPES-1)}
 {}

Очевидно, псевдокод, но вы поймете, что массив можно инициализировать напрямую, оставив конструктор без тела, что очень удобно.Это также означает, что член массива _names может иметь значение const, что дополнительно способствует правильному использованию этого вспомогательного класса.

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

Есть ли способ написать список инициализатора массива в стиле C ++ 11, который имеет гибкую длину и определяется выражением?Другой простой пример:

constexpr int numberCount = 10;
std::string numbers[] = {std::to_string(1), std::to_string(2), ... , std::to_string(numberCount)};

Опять выражение вместо цикла.

Я задаю этот вопрос не потому, что пытался радикально улучшить производительность, а потому, что хочучтобы узнать о новых, изящных, особенностях C ++ 14 и более поздних версий.

Ответы [ 4 ]

6 голосов
/ 06 июня 2019

вместо массива C используйте std::array, тогда вы могли бы написать свою функцию, возвращающую std::array, и тогда ваш член может быть const:

std::array<std::string, NUMBER_OF_TYPES> build_names()
{
    std::array<std::string, NUMBER_OF_TYPES> names;
    for (int i = 0; i < NUMBER_OF_TYPES; ++i)
    {
          names[i] = std::string("Type ") + getTypeSuffix(i);
    }
    return names;
}


class MyNames
{
  MyNames() : _names(build_names()) {}
  const char* operator[](int type) const { return _names[(int)type].c_str(); }

private:
  const std::array<std::string, NUMBER_OF_TYPES> _names;
};

Теперь у вас есть std::array, вы можете использовать шаблон переменной вместо цикла, что-то вроде (std::index_sequence материал C ++ 14, но может быть реализован в C ++ 11):

template <std::size_t ... Is> 
std::array<std::string, sizeof...(Is)> build_names(std::index_sequence<Is...>)
{
     return {{ std::string("Type ") + getTypeSuffix(i) }};
}

и затем вызвать его:

MyNames() : _names(build_names(std::make_index_sequence<NUMBER_OF_TYPES>())) {}
3 голосов
/ 06 июня 2019

Вы можете отложить до функции инициализации:

std::array<std::string, NUMBER_OF_TYPES> initializeNames()
{
    std::array<std::string, NUMBER_OF_TYPES> names;
    for (int i = 0; i < NUMBER_OF_TYPES; ++i) {
        names[i] = std::string("Type ") + getTypeSuffix(i);
    }
    return names;
}

const char* getReadableTypeName(int type) 
{
  static auto const names = initializeNames();
  return names[type].c_str();
}

, который может быть лямбдой, вызываемой немедленно:

static auto const names = []{
    std::array<std::string, NUMBER_OF_TYPES> names;
    // ...
    return names;
}();

или вам действительно нужно требование array? В любом случае мы создаем строки, поэтому я не понимаю, тогда вы можете просто использовать range-v3:

char const* getReadableTypeName(int type) {
    static auto const names =
        view::iota(0, NUMBER_OF_TYPES)
        | view::transform([](int i){ return "Type "s + getTypeSuffix(i); })
        | ranges::to<std::vector>();
    return names[type].c_str():
}
2 голосов
/ 06 июня 2019

Вы можете использовать std::make_integer_sequence и делегирующий конструктор в C ++ 14 (реализации std::make_integer_sequence существуют в C ++ 11, так что это не совсем C ++ 14), чтобы получить пакет параметров шаблона целых чисел

#include <string>
#include <utility>

#define NUMBER_OF_TYPES 23

const char* getTypeSuffix(int index);

class MyNames
{
  MyNames() : MyNames(std::make_integer_sequence<int, NUMBER_OF_TYPES>{}) {}

  template<int... Indices>
  MyNames(std::integer_sequence<int, Indices...>) : _names{ (std::string("Type ") + getTypeSuffix(Indices))... } {}

  const char* operator[](int type) { return _names[(int)type].c_str(); }

private:
  const std::string _names[NUMBER_OF_TYPES];
};

Это означает, что по умолчанию не создается ни одной строки.

1 голос
/ 06 июня 2019

Поскольку вам не терпится использовать новые функции, давайте использовать range-v3 (скоро станет библиотекой ranges в C ++ 2a), чтобы написать действительно короткий код:

const char* getReadableTypeName(int type) 
{
    static const std::vector<std::string> names =
        view::ints(0, 23) | view::transform([](int i) {
            return "Type " + std::to_string(i);
        });
    return names[type].c_str();
}

https://godbolt.org/z/UVoENh

...