#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, если ситуация станет слишком разреженной), поэтому я принимаю это. Спасибо за предложение!