Поиск дубликатов в массиве во время компиляции - PullRequest
0 голосов
/ 20 января 2019

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

Во-первых, у меня есть структура, которая содержит одну конфигурацию:

struct Arg_Opt_Tuple {
  std::string_view mc{}; // multichar ie "help" 
  char sc{}; // singlechar ie 'h' 
  bool is_flag{}; 
};

Теперьскажем, я хотел создать функцию (или, в конечном счете, конструктор объекта), которая возвращает фиксированный размер std :: array, но также выполняет некоторую проверку во время компиляции на наличие дубликатов или пустых значений, моя цель - вызвать ее в некоторыхмода, подобная этой:

constexpr auto ARG_COUNT = 4U;
constexpr auto opts = checked_arr<ARG_COUNT>(
  Arg_Opt_Tuple{"hello", 'h', false},
  Arg_Opt_Tuple{"world", 'g', true},
  Arg_Opt_Tuple{"goodbye", 'h', false}, // <- static_assert('h' == 'h')
  Arg_Opt_Tuple{"hello", 'r', false} // <- static_assert(sv.compare("hello") == 0)
);

Моя первая попытка состояла в том, чтобы использовать std :: initializer_list, но столкнулась с некоторыми проблемами, и после некоторого поиска в Google пришел к выводу, что это не правильно делать здесь в сочетании сconstexpr.Моя текущая попытка включает шаблон с переменным числом аргументов:

template <std::size_t N, typename... T>
constexpr std::array<Arg_Opt_Tuple, N> checked_arr(T... list) {
  static_assert(N == sizeof...(T));
  return {list...};
}

Это работает, но совершенно лишним является просто инициализация массива, я действительно хочу, чтобы это выполняло некоторую проверку времени компиляции.Для дубликатов или ошибочных значений во время выполнения это просто, вы можете просто просмотреть и сравнить или выполнить std :: find или что-то еще, но все это не работает во время компиляции, т. Е. (Я знаю, это уродливо, но вы понимаете,):

for (std::size_t src_i = 0; src_i < ARG_COUNT; ++src_i) {
  for (std::size_t check_i = 0; check_i < ARG_COUNT; ++check_i) {
    // skip checking self
    if (check_i == src_i) {
      continue;
    }
    // doesnt work obviously
    static_assert(opts[src_i].sc != opts[check_i].sc);
  }
}

Так как трудно это будет достичь?Это плохой дизайн?Любые указатели были бы прекрасны.

Ответы [ 2 ]

0 голосов
/ 20 января 2019

Не совсем то, что вы просили, но ... если вы проверяете дубликаты внутри checked_arr() и вы бросаете исключение, если вы находите его, у вас есть исключение при выполнении checked_arr() времени выполнения и ошибка компиляции при выполнении его компиляции-time.

Я имею в виду ... вы можете написать

template <std::size_t N0 = 0u, typename ... Ts,
          std::size_t N = (N0 > sizeof...(Ts)) ? N0 : sizeof...(Ts)>
constexpr auto checked_arr (Ts ... args)
 {
   std::array<Arg_Opt_Tuple, N> arr {args...};

   for ( auto i = 0u ; i < sizeof...(Ts) ; ++i )
      for ( auto j = 0u; j < sizeof...(Ts) ; ++j )
         if ( (i != j) && (arr[i].sc == arr[j].sc) )
             throw std::runtime_error("equal sc");

   return arr;
 }

(не по теме: соблюдайте трюк с N0 и N: так что вы должны явно N0только когда больше sizeof...(Ts))

Если вы позвоните

constexpr auto opts = checked_arr(
   Arg_Opt_Tuple{"hello", 'h', false},
   Arg_Opt_Tuple{"world", 'g', true},
   Arg_Opt_Tuple{"goodbye", 'h', false},
   Arg_Opt_Tuple{"hello", 'r', false}
);

, вы получите ошибку компиляции;в g ++

prog.cc:26:42: error: expression '<throw-expression>' is not a constant expression
   26 |       throw std::runtime_error("equal sc");
      |                                          ^

Ниже приведен пример полной компиляции C ++ 17 (не компилируется, если вы помещаете коллизию в opts)

#include <array>
#include <string>
#include <exception>

struct Arg_Opt_Tuple {
  std::string_view mc{}; // multichar ie "help" 
  char sc{}; // singlechar ie 'h' 
  bool is_flag{}; 
};

template <std::size_t N0 = 0u, typename ... Ts,
          std::size_t N = (N0 > sizeof...(Ts)) ? N0 : sizeof...(Ts)>
constexpr auto checked_arr (Ts ... args)
 {
   std::array<Arg_Opt_Tuple, N> arr {args...};

   for ( auto i = 0u ; i < sizeof...(Ts) ; ++i )
      for ( auto j = 0u; j < sizeof...(Ts) ; ++j )
         if ( (i != j) && (arr[i].sc == arr[j].sc) )
             throw std::runtime_error("equal sc");

   return arr;
 }

int main ()
 {
    constexpr auto opts = checked_arr(
       Arg_Opt_Tuple{"hello", 'h', false},
       Arg_Opt_Tuple{"world", 'g', true},
       Arg_Opt_Tuple{"goodbye", 'i', false},
       Arg_Opt_Tuple{"hello", 'r', false}
    );
 }

Но я предлагаю просто инициализироватьмассив как constexpr переменная

constexpr std::array opts {
    Arg_Opt_Tuple{"hello", 'h', false},
    Arg_Opt_Tuple{"world", 'g', true},
    Arg_Opt_Tuple{"goodbye", 'i', false},
    Arg_Opt_Tuple{"hello", 'r', false}
};

и проверить его, вызывая функцию constexpr внутри static_assert()

static_assert( checkOpts(opts) );

, где checOpts() - это что-то вроде

template <std::size_t N>
constexpr bool checkOpts (std::array<Arg_Opt_Tuple, N> const & arr)
 {
   for ( auto i = 0u ; i < N ; ++i )
      for ( auto j = 0u; j < N ; ++j )
         if ( (i != j) && (arr[i].sc == arr[j].sc) )
             return false;

   return true;
 }
0 голосов
/ 20 января 2019

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

Простые циклы работают:

template <typename T> constexpr bool has_duplicates(const T *array, std::size_t size)
{
    for (std::size_t i = 1; i < size; i++)
        for (std::size_t j = 0; j < i; j++)
            if (array[i] == array[j])
                return 1;
    return 0;
}

constexpr int foo[] = {1, 2, 3, 4};
static_assert(!has_duplicates(foo, 4));

Если вы хотите иметь static_assert внутри функции, вам нужно вместо этого передать массив в качестве параметра шаблона:

template <auto &array> constexpr void assert_has_no_duplicates()
{
    constexpr std::size_t size = std::extent_v<std::remove_reference_t<decltype(array)>>;
    static_assert(!has_duplicates(array, size));
}

constexpr int foo[] = {1, 2, 3, 4};

int main()
{
    assert_has_no_duplicates<foo>();
}

Или, если хотите, std::array s:

template <auto &array> constexpr void assert_has_no_duplicates()
{
    static_assert(!has_duplicates(array.data(), array.size()));
}

constexpr std::array<int,4> foo = {1, 2, 3, 4};

int main()
{
    assert_has_no_duplicates<foo>();
}
...