Передача аргумента std :: array с размером, ограниченным расширяемым набором размеров - PullRequest
0 голосов
/ 16 мая 2018

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

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

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

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

#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. Geв общих чертах: Есть ли другой, лучший подход?

Ответы [ 7 ]

0 голосов
/ 16 мая 2018

У вас может быть шаблон 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 вы могли бы использовать концепцию для получения более четких сообщений об ошибках;)

0 голосов
/ 17 мая 2018

Использование 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 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>;

Демонстрация в режиме онлайн

Какой бы путь реализации ни был выбран, использование 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) {
         ^
0 голосов
/ 16 мая 2018

Лучший способ решить эту проблему - написать черту нестандартного типа:

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 s, поэтому вам нужна только одна специализация.

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

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

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{};
0 голосов
/ 16 мая 2018

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

#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
}
0 голосов
/ 16 мая 2018

Лично я бы просто вручную ввел допустимые размеры в 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 ++ будут недовольны этимно он выполняет свою работу.

0 голосов
/ 16 мая 2018

К сожалению, ваши перечисления не являются непрерывными, поэтому вы не можете просто перебрать перечисление, и вам придется обрабатывать все случаи индивидуально. Поскольку размеры известны во время компиляции, вы можете 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!
}
0 голосов
/ 16 мая 2018

немного вертелся, и это уменьшило работу: возможно, это поможет:

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;
}
...