在编译时在数组中查找重复项

时间:2019-01-19 22:40:48

标签: c++ c++11 variadic-templates constexpr

我正在尝试学习一些更现代的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,但是遇到了一些问题,经过一番谷歌搜索后得出的结论是,在这里与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 :(得分:6)

  

对于运行时重复或错误的值很容易,您可以循环浏览并比较或执行std :: find或不执行任何操作,但是这些似乎在编译时都不起作用

普通循环确实有效:

template <typename T> constexpr bool has_duplicates(const T *array, std::size_t size)
{
    for (std::size_t i = 0; i < size-1; i++)
        for (std::size_t j = i+1; j < size; 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

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>();
}

答案 1 :(得分:0)

与您要求的不完全相同,但是...如果您检查checked_arr()中的重复项,并且发现异常,则抛出异常,则在执行checked_arr()运行时会出现异常,而在执行时出现编译错误您可以在编译时执行。

我的意思是...你可以写

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

(主题:观察N0N的技巧:因此,只有当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} );

如果您致电

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

出现编译错误;在g ++中

opts

以下是完整的C ++ 17示例(如果在#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; }

Transparent