ассоциативный массив во время компиляции c

#c #templates

#c #шаблоны

Вопрос:

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

 MyArray<std::string, 1, 3, 7> a;
a[1] = "hello";
a[3] = "world";
a[7] = "!";

// runtime error
// a[4] = "?";
  

Под капотом MyArray<std::string, 1, 3, 7> в основном было бы эквивалентно следующему:

 class C
{
  private:

    std::string _values[3];

  public:

    std::string amp; operator[](std::size_t index)
    {
      switch(index)
      {
        case 1: return _values[0];
        case 3: return _values[1];
        case 7: return _values[2];
        default: /* some error condition */
      }
    }
};
  

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

Критерии:

  • Должен работать, если индексы неизвестны во время компиляции
  • Access должен использовать оператор switch или что-то эквивалентное, а не линейный поиск во время выполнения по разрешенным ключам.
  • Не хотите хэш-карту с потенциальными коллизиями пакетов, такими как std::unordered_map

Я также был бы рад ссылке, которая объясняет, как написать такой шаблонный код.

Комментарии:

1. Что касается вашего первого пункта: вы имеете в виду доступные индексы из MyArray<std::string, 1, 3, 7> или запрошенные индексы, например, из a[1] = "hello"; ?

2. @Quentin: Я имею в виду, что должно компилироваться что-то вроде int i; std::cin >> i; std::cout << a[i] .

3. Реализовать случай переключения легко, но это линейный поиск. Вы ищете идеальный хэш?

4. Без линейного требования… вы могли бы использовать std::map , создать требуемые значения с помощью конструктора и во время выполнения вызвать исключение, обращающееся к карте через индекс, соответствующий отсутствующему значению. Проблемы в том, что std::map является (если я правильно помню) O(n log (n))

5. @AtnNn: Я надеялся, что это будет так, но, похоже, по крайней мере, g просто с радостью сгенерирует огромный линейный поиск для эквивалентного блока if / elseif /else: godbolt.org/z/ecTz19

Ответ №1:

хорошо, если вы не возражаете против возможной огромной стоимости пространства, вы можете сделать это следующим образом:

 template<typename T, size_t Head, size_t... Tails>
class MagicArray{
    std::array<T, 1   sizeof...(Tails)> data;
    static constexpr size_t max(size_t a, size_t b) noexcept{
        return (a < b) ? b : a;
    }
    template<typename head, typename... tails>
    static constexpr size_t max(head a, tails... b) noexcept{
        if constexpr (sizeof...(b) == 0)
            return a;
        else
            return max(a, max(b...));
    }
    static constexpr size_t value_max = max(Head, Tails...);
    template<size_t addition, size_t I, size_t A, size_t... B>
    static constexpr size_t find() noexcept{
        if constexpr (I == A)
            return addition;
        else if constexpr (sizeof...(B) == 0)
            return value_max;
        else
            return find<addition   1, I, B...>();
    }
    template<size_t... Is>
    static constexpr std::array<size_t, value_max   1> generation(std::index_sequence<Is...>*) noexcept{
        return { (find<0, Is, Head, Tails...>())... };
    }
    static constexpr std::array<size_t, value_max   1> mapping = generation((std::make_index_sequence<value_max   1>*)nullptr);
public:
    Tamp; operator[](size_t index){
        if (index > value_max || mapping[index] == value_max)
            throw 0;
        else
            return data[mapping[index]];
    }
    T constamp; operator[](size_t index) const{
        if (index > value_max || mapping[index] == value_max)
            throw 0;
        else
            return data[mapping[index]];
    }
};

int main(){
    MagicArray<std::string, 1, 3, 7> a;
    a[1] = "Hello";
    a[3] = "World";
    a[7] = "!";
    a[9] = "Boooooooooooooooom!";
}
  

Комментарии:

1. Я начинаю видеть, что то, что я хочу, невозможно (за исключением использования макросов вместо шаблонов, которых я бы предпочел избегать), и это действительно сработает для моего варианта использования (возможно, с переходом на std::unordered_map, если ситуация станет слишком разреженной), поэтому я принимаю это. Спасибо за предложение!