Передача аргумента std :: array с размером, ограниченным расширяемым набором размеров

Как лучше всего реализовать единственную функцию, которая принимает два аргумента std::array<int, [size]>, каждый с размером сдержанный на соответствующий набор значений, известных во время компиляции?

  • Функция должна принимать только массивы с размерами, полученными из заданного набора (enum / macro / etc)
  • Наборы допустимых «размеров» массива могут быть изменены в будущем и могут быть большими (эффективно предотвращая перегрузку функций).
  • Сама функция должна оставаться неизменной независимо от изменений в наборах допустимых «размеров» массива.

Вопрос «Передача в функцию std :: array неизвестного размера», хотя и похож, не имеет прямого отношения.

Следующее работает в C++ 14, но кажется излишне избыточным и беспорядочным:

#include <type_traits>
#include <array>

// Add legal/allowable sizes for std::array<> "types" here
// Note: Not married to this; perhaps preprocessor instead?
enum class SizesForArrayX : size_t { Three = 3, Four, Forty = 40 };
enum class SizesForArrayY : size_t { Two = 2, Three, EleventyTwelve = 122 };

// Messy, compile-time, value getter for the above enum classes
template <typename S>
constexpr size_t GetSizeValue(const S size)
{ return static_cast<std::underlying_type_t<S>>(size); }

// An example of the function in question; is Template Argument Deduction
//  possible here?
// Note: only arrays of "legal"/"allowable" sizes should be passable
template <SizesForArrayX SX, SizesForArrayY SY>
void PickyArrayHandler(
    const std::array<int, GetSizeValue(SX)>& x,
    const std::array<int, GetSizeValue(SY)>& y)
{
    // Do whatever
    for (auto& i : x) i = 42;
    for (auto& i : y) while (i --> -41) i = i;
}

Вызов вышеуказанного:

int main()
{
    // Declare & (value-)initialize some arrays
    std::array<int, GetSizeValue(SizesForArrayX::Forty)> x{};
    std::array<int, GetSizeValue(SizesForArrayY::Two>) y{};

    //PickyArrayHandler(x, y); // <- Doesn't work; C2672, C2783

    // This works & handles arrays of any "allowable" size but the required
    //  template params are repetitions of the array declarations; ick
    PickyArrayHandler<SizesForArrayX::Forty, SizesForArrayY::Two>(x, y);
}

... что некрасиво, неэлегантно, медленно компилируется и требует, чтобы заявленный размер массива соответствовал явному переданному "размеру" для шаблона функции PickyArrayHandler.

  1. Для конкретного примера выше: Есть ли способ для шаблона PickyArrayHandler определить размеры переданных массивов?

  2. Вообще говоря: Есть ли другой, лучший подход?

если вы удалите "класс" из перечисления, вам не понадобится 'GetSizeValue' и приведение

skeller 16.05.2018 01:07
static_assert внутри функции.
Henri Menke 16.05.2018 01:07

@skeller без строго типизированного перечисления, как ограничить размеры массива?

quasinormalized 16.05.2018 01:28

@ Анри, спасибо. Я не понимаю, как это отразится на 2-м и 3-м пунктах

quasinormalized 16.05.2018 01:46

@quasinormalized Это невозможно с перечислением. Вы не можете перебирать непрерывное перечисление.

Henri Menke 16.05.2018 01:53

Отредактирован заголовок, чтобы удалить вводящее в заблуждение "перечисление"

quasinormalized 16.05.2018 17:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
253
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

немного покрутил и получил эту уменьшенную работу: может быть, это помогает:

enum SizesForArrayX : size_t { Three = 3, Four, Forty = 40 };
enum SizesForArrayY : size_t { Two = 2, EleventyTwelve = 122 };

template <size_t TX, size_t TY>
void PickyArrayHandler(
    const std::array<int, TX>& x,
    const std::array<int, TY>& y)
{
    // Do whatever
}

int main()
{
    // Declare & (value-)initialize some arrays
    std::array<int, SizesForArrayX::Forty> x{};
    std::array<int, SizesForArrayY::Two> y{};

    PickyArrayHandler(x, y); 

    return 0;
}

Но тогда вы также можете сделать это с массивом любого размера; он ничего не ограничивает.

Daniel H 16.05.2018 01:19

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

#include <array>

enum SizesForArrayX : size_t { Three = 3, Four, Forty = 40 };
enum SizesForArrayY : size_t { Two = 2, EleventyTwelve = 122 };

template <size_t TX, size_t TY>
void PickyArrayHandler(const std::array<int, TX> &x,
                       const std::array<int, TY> &y)
{
    static_assert(TX == Three || TX == Four || TX == Forty,
                  "Size mismatch for x");
    static_assert(TY == Two || TY == EleventyTwelve, "Size mismatch for y");
    // Do whatever
}

int main()
{
    // Declare & (value-)initialize some arrays
    std::array<int, SizesForArrayX::Forty> x{};
    std::array<int, SizesForArrayY::Two> y{};

    PickyArrayHandler(x, y);
    PickyArrayHandler(std::array<int, 4>{}, std::array<int, 2>{});
  //PickyArrayHandler(std::array<int, 1>{}, std::array<int, 5>{}); // BOOM!
}

Лично я бы просто вручную ввел допустимые размеры в static_assert внутри PickyArrayHandler. Если это не вариант, потому что размеры будут использоваться в других частях вашей программы, а вы придерживаетесь принципа DRY, я бы использовал препроцессор.

#define FOREACH_ALLOWABLE_X(SEP_MACRO) \
    SEP_MACRO(3)    \
    SEP_MACRO(4)    \
    SEP_MACRO(40)   \

#define FOREACH_ALLOWABLE_Y(SEP_MACRO) \
    SEP_MACRO(2)    \
    SEP_MACRO(3)    \
    SEP_MACRO(122)  \


#define COMMA_SEP(NUM) NUM,
#define LOGIC_OR_SEP_X(NUM) N1 == NUM ||
#define LOGIC_OR_SEP_Y(NUM) N2 == NUM ||
#define END_LOGIC_OR false

// some arrays with your sizes incase you want to do runtime checking
namespace allowable_sizes
{
    size_t x[] {FOREACH_ALLOWABLE_X(COMMA_SEP)};
    size_t y[] {FOREACH_ALLOWABLE_Y(COMMA_SEP)};
}

template <size_t N1, size_t N2>
void PickyArrayHandler(const std::array<int, N1>& x, const std::array<int, N2>& y)
{
    static_assert(FOREACH_ALLOWABLE_X(LOGIC_OR_SEP_X) END_LOGIC_OR);
    static_assert(FOREACH_ALLOWABLE_Y(LOGIC_OR_SEP_Y) END_LOGIC_OR);

    // do whatever
}

#undef FOREACH_ALLOWABLE_X
#undef FOREACH_ALLOWABLE_Y
#undef COMMA_SEP
#undef LOGIC_OR_SEP_X
#undef LOGIC_OR_SEP_Y
#undef END_LOGIC_OR

Некоторые пуристы C++ будут недовольны этим, но он выполняет свою работу.

Ответ принят как подходящий

Поскольку вы не кажетесь разборчивым в том, как определяются допустимые размеры, вы можете использовать признаки типа

#include <array>

template <size_t N> struct valid_size1 { enum { value = false }; };
template <size_t N> struct valid_size2 { enum { value = false }; };

template <> struct valid_size1<3> { enum { value = true }; };
template <> struct valid_size1<4> { enum { value = true }; };
template <> struct valid_size1<40> { enum { value = true }; };

template <> struct valid_size2<2> { enum { value = true }; };
template <> struct valid_size2<122> { enum { value = true }; };

template <size_t TX, size_t TY>
void PickyArrayHandler(const std::array<int, TX> &x,
                       const std::array<int, TY> &y)
{
  static_assert(valid_size1<TX>::value, "Size 1 is invalid");
  static_assert(valid_size2<TY>::value, "Size 2 is invalid");
    // Do whatever
}

int main()
{
    // Declare & (value-)initialize some arrays
    std::array<int, 40> x{};
    std::array<int, 2> y{};

    PickyArrayHandler(x, y);
    PickyArrayHandler(std::array<int, 4>{}, std::array<int, 2>{});
    // PickyArrayHandler(std::array<int, 1>{}, std::array<int, 5>{}); // BOOM!
}

Вот решение с использованием массива:

#include <iostream>
#include <array>

constexpr size_t valid_1[] = { 3, 4, 40 };
constexpr size_t valid_2[] = { 2, 122 };

template <size_t V, size_t I=0> 
struct is_valid1 { static constexpr bool value = V==valid_1[I] || is_valid1<V,I+1>::value; };

template <size_t V, size_t I=0> 
struct is_valid2 { static constexpr bool value = V==valid_2[I] || is_valid2<V,I+1>::value; };

template <size_t V>
struct is_valid1<V, sizeof(valid_1)/sizeof(valid_1[0])>
{static constexpr bool value = false; };

template <size_t V>
struct is_valid2<V, sizeof(valid_2)/sizeof(valid_2[0])>
{static constexpr bool value = false; };

template <size_t TX, size_t TY>
void PickyArrayHandler(const std::array<int, TX> &x,
                       const std::array<int, TY> &y)
{
  static_assert(is_valid1<TX>::value, "Size 1 is invalid");
  static_assert(is_valid2<TY>::value, "Size 2 is invalid");
    // Do whatever
}

Это работает, хотя и с предупреждениями о локальных var для isValid, которые не указаны. Не могли бы вы более подробно объяснить, что происходит в { typename valid_size1<TX>::type isValid; }, так как вы никогда не баловались типичными характеристиками?

quasinormalized 16.05.2018 18:11

@quasinormalized Чтобы создать экземпляр шаблона, он должен иметь возможность создать переменную типа valid_size1 <TX> :: type. Если тип valid_size1 <TX> :: type существует, он завершается успешно. В противном случае это не так. Переменные не используются, поэтому вы получите предупреждение.

agbinfo 16.05.2018 18:40

это элегантно, мне это нравится :)

skeller 16.05.2018 19:00

@agbinfo Круто, спасибо! В настоящее время я играю с гибридом этого ответа и Стивена, чтобы посмотреть, могу ли я устранить предупреждения компилятора. Есть ли у вас какие-нибудь идеи?

quasinormalized 16.05.2018 20:58

Обновлено для удаления предупреждений

agbinfo 16.05.2018 21:55

Хороший. Хотя я думаю, что вы могли пропустить enum { value = false }; внутри скобок исходного шаблона структуры?

quasinormalized 16.05.2018 22:30

Другой вопрос: это просто соглашение о явном шаблоне неиспользуемого N? template <size_t /*N*/> struct valid_size1 { enum { value = false }; }; вроде работает нормально. Как называется этот синтаксис, чтобы я мог исследовать?

quasinormalized 16.05.2018 22:36

value = false не нужен, но не повредит - возможно, вы получите лучшую диагностику, но я не проверял. Если он опущен, отсутствие идентификатора value приведет к сбою шаблона. Что касается неиспользованного N, то мне известно не о каком-либо соглашении.

agbinfo 16.05.2018 22:51

@agbinfo Без value = false компиляция прерывается с 'необъявленный идентификатор' в шаблоне вместо сбоя в static_assert(), как предполагалось. Если вы не возражаете против этого небольшого обновления, я бы принял этот ответ как полный. :)

quasinormalized 16.05.2018 23:09

Обновлено с помощью value = false в шаблоне по умолчанию

agbinfo 16.05.2018 23:38

Я добавил решение с массивом в стиле C. Думаю, так лучше.

agbinfo 17.05.2018 01:16

На мой взгляд, лучший способ решить эту проблему - написать свойство настраиваемого типа:

template <std::underlying_type_t<SizesForArrayX> SX>
struct is_size_x {
    static constexpr bool value = false;
};

template <>
struct is_size_x<static_cast<std::underlying_type_t<SizesForArrayX>>(SizesForArrayX::Forty)>{

    static constexpr bool value = true;
};

Я бы поместил их прямо под декларациями enum class, чтобы было легко проверить, все ли они у вас есть. Кто-нибудь более умный, чем я, вероятно, смог бы найти способ даже сделать это с вариативными template, так что вам понадобится только одна специализация.

Это утомительно, но если у вас небольшой набор значений, он должен быть достаточно быстрым и легким для использования в модульных тестах. Другой приятный момент в этом подходе заключается в том, что если у вас есть несколько функций, которым требуется один из этих специальных размеров, вам не нужно копировать / вставлять static_assert.

С чертами типа ваша функция становится тривиальной:

template <std::size_t SX, std::size_t SY>
void PickyArrayHandler(
    std::array<int, SX>& x,
    std::array<int, SY>& y)
{
    static_assert(is_size_x<SX>::value, "Invalid size SX");
    static_assert(is_size_y<SY>::value, "Invalid size SY");
    // Do whatever
    for (auto& i : x) i = 42;
    for (auto& i : y) while (i --> -41) i = i;
}

Наконец, вы можете создать псевдоним типа, чтобы в первую очередь избежать создания недопустимых array:

template <typename T, SizesForArrayX SIZE>
using XArray =
    std::array<T, static_cast<std::underlying_type_t<SizesForArrayX>>(SIZE)>;

template <typename T, SizesForArrayY SIZE>
using YArray =
    std::array<T, static_cast<std::underlying_type_t<SizesForArrayY>>(SIZE)>;

Это помешает вам объявить array, если это не утвержденный размер:

XArray<int, SizesForArrayX::Forty> x{};
YArray<int, SizesForArrayY::Two> y{};

Этот отличный ответ заслуживает большего, чем то, что может предложить моя репутация ниже 15, которую может предложить невидимое голосование за. Любой подсказки по устранению лишних специализаций, пока я экспериментирую с гибридом этого ответа & @ agbinfo? Классы перечисления были просто моим первым ударом по проблеме и не должны сохраняться, пока размеры массива ограничены.

quasinormalized 16.05.2018 20:54

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

Stephen Newell 16.05.2018 22:07

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

#include <array>
#include <type_traits>

// Forward template declaration without definition.
template <class T, T N, T... Sizes>
struct is_one_of;

// Specialization when there is a single value: Ends of the recursion,
// the size was not found, so we inherit from std::false_type.
template <class T, T N>
struct is_one_of<T, N>: public std::false_type {};

// Generic case definition: We inherit from std::integral_constant<bool, X>, where X
// is true if N == Size or if N is in Sizes... (via recursion).
template <class T, T N, T Size, T... Sizes>
struct is_one_of<T, N, Size, Sizes... >: 
    public std::integral_constant<
        bool, N == Size || is_one_of<T, N, Sizes... >::value> {};

// Alias variable template, for simpler usage.
template <class T, T N, T... Sizes>
constexpr bool is_one_of_v = is_one_of<T, N, Sizes... >::value;

template <std::size_t N1, std::size_t N2,
          std::enable_if_t<
                (is_one_of_v<std::size_t, N1, 3, 4, 40> 
                && is_one_of_v<std::size_t, N2, 2, 3, 122>), int> = 0>
void PickyArrayHandler(
    const std::array<int, N1>& x,
    const std::array<int, N2>& y)
{
}

Тогда вы можете просто:

PickyArrayHandler(std::array<int, 3>{}, std::array<int, 122>{}); // OK
PickyArrayHandler(std::array<int, 2>{}, std::array<int, 3>{}); // NOK

В C++ 17 вы могли бы (я думаю) заменить is_one_of на:

template <auto N, auto... Sizes>
struct is_one_of;

... и автоматически выводит std::size_t.


В C++ 20 вы можете использовать концепцию более четких сообщений об ошибках;)

Не уверен, что я достаточно знаком с шаблонами, чтобы следовать вашему коду; но +1 для NOK, который входит в мой лексикон сегодня и навсегда.

quasinormalized 16.05.2018 21:41

@quasinormalized Я добавил некоторую информацию - в основном у вас есть рекурсия шаблонов, которая проверяет, является ли переданный вами первый размер (N) одним из других Sizes. Я сделал общий шаблон, поэтому есть T, но у вас может быть более простой шаблон только для std::size_t.

Holt 16.05.2018 21:46

@quasinormalized Идея довольно проста для понимания, если вы знакомы с рекурсией. Базовый случай - когда Sizes... пуст (первая специализация), и в этом случае результат ложный (std::false_type), иначе вы проверяете, соответствует ли Size первого размера N, или вы рекурсивно создаете экземпляр шаблона с остальными размерами Sizes....

Holt 16.05.2018 21:47

Использование static_assert для недопустимых размеров нет - хорошее решение, потому что оно плохо работает с SFINAE; то есть средства TMP, такие как std::is_invocable и идиома обнаружения, будут возвращать ложные срабатывания для вызовов, которые на самом деле всегда приводят к ошибке. Намного лучше использовать SFINAE для удаления недопустимых размеров из набора перегрузки, что приведет к чему-то вроде следующего:

template<std::size_t SX, std::size_t SY,
         typename = std::enable_if_t<IsValidArrayXSize<SX>{} && IsValidArrayYSize<SY>{}>>
void PickyArrayHandler(std::array<int, SX> const& x, std::array<int, SY> const& y) {
    // Do whatever
}

Сначала нам нужно объявить наши допустимые размеры; Я не вижу здесь каких-либо преимуществ более строгой типизации, поэтому для списка целых чисел во время компиляции std::integer_sequence отлично работает и очень легкий:

using SizesForArrayX = std::index_sequence<3, 4, 40>;
using SizesForArrayY = std::index_sequence<2, 3, 122>;

Теперь о свойствах IsValidArraySize ... Самый простой путь - использовать правила C++ 14 Relaxed-constexpr и выполнить простой линейный поиск:

#include <initializer_list>

namespace detail {
    template<std::size_t... VSs>
    constexpr bool idx_seq_contains(std::index_sequence<VSs...>, std::size_t const s) {
        for (auto const vs : {VSs...}) {
            if (vs == s) {
                return true;
            }
        }
        return false;
    }
} // namespace detail

template<std::size_t S>
using IsValidArrayXSize
  = std::integral_constant<bool, detail::idx_seq_contains(SizesForArrayX{}, S)>;
template<std::size_t S>
using IsValidArrayYSize
  = std::integral_constant<bool, detail::idx_seq_contains(SizesForArrayY{}, S)>;

Online Demo

Однако, если время компиляции вообще вызывает беспокойство, я подозреваю, что следующее будет лучше, если потенциально менее ясно:

namespace detail {
    template<bool... Bs>
    using bool_sequence = std::integer_sequence<bool, Bs...>;

    template<typename, std::size_t>
    struct idx_seq_contains;

    template<std::size_t... VSs, std::size_t S>
    struct idx_seq_contains<std::index_sequence<VSs...>, S>
      : std::integral_constant<bool, !std::is_same<bool_sequence<(VSs == S)...>,
                                                   bool_sequence<(VSs, false)...>>{}>
    { };
} // namespace detail

template<std::size_t S>
using IsValidArrayXSize = detail::idx_seq_contains<SizesForArrayX, S>;
template<std::size_t S>
using IsValidArrayYSize = detail::idx_seq_contains<SizesForArrayY, S>;

Online Demo

Какой бы маршрут реализации ни был выбран, использование SFINAE таким образом позволяет получать очень хорошие сообщения об ошибках - например, для PickyArrayHandler(std::array<int, 5>{}, std::array<int, 3>{}); текущий Clang 7.0 ToT дает следующее, сообщая вам, что размер массива который недействителен:

error: no matching function for call to 'PickyArrayHandler'
    PickyArrayHandler(std::array<int, 5>{}, std::array<int, 3>{});
    ^~~~~~~~~~~~~~~~~

note: candidate template ignored: requirement 'IsValidArrayXSize<5UL>{}' was not satisfied [with SX = 5, SY = 3]
    void PickyArrayHandler(std::array<int, SX> const& x, std::array<int, SY> const& y) {
         ^

Другие вопросы по теме