【问题标题】:Generic utility to create aribtrary tuples of integral_constants创建integral_constants的任意元组的通用实用程序
【发布时间】:2017-09-04 20:50:34
【问题描述】:

利用Scott Schurr's str_const 我有一个constexpr 字符串。

class StrConst
{
public:
    template<size_t N>
    constexpr StrConst(const char (&str)[N])
        : str_(str)
        , len_(N - 1)
    {
        static_assert(N > 1, "not a string");
    }

    constexpr operator const char*() const
    {
        return str_;
    }

    constexpr size_t size() const
    {
        return len_;
    }

    constexpr char operator[] (size_t i) const
    {
        return i < len_ ? str_[i] : throw std::out_of_range("invalid index");
    }

private:
    const char* const str_;
    const size_t      len_;
};

我还有另一个 constexpr 函数,它返回在字符串中找到的第一个插入符号的位置,从位置 n 开始:

constexpr int caretPos(const StrConst& str, size_t n = 0)
{
    if (n == str.size())
        return -1;

    if (str[n] == '^')
        return n;

    return caretPos(str, n+1);
}

我可以使用caretPos 的结果为std::integral_constantsstd::tuple 创建一个typedef,其中元组的大小是在字符串中找到的插入符号的数量,每个元组元素都是一个整数常量其值为插入符号在字符串中的位置。

这里我手动构造了这个元组:

int main()
{
    constexpr StrConst s("hello^world^");

    constexpr int pos1 = caretPos(s);
    constexpr int pos2 = caretPos(s, pos1+1);

    using P1 = std::integral_constant<int, pos1>;
    using P2 = std::integral_constant<int, pos2>;

    using PosTuple = std::tuple<P1, P2>;

    static_assert(std::tuple_element_t<0, PosTuple>::value == 5, "");
    static_assert(std::tuple_element_t<1, PosTuple>::value == 11, "");
}

问题:

我现在想将它推广到任何具有任意数量插入符号的输入字符串。

template<size_t... Ns>
using PosTuple = std::tuple<std::integral_constant<int, Ns>...>;

如何使用caretPos或其他方式生成此处所需的Ns...序列?

Working example

【问题讨论】:

    标签: c++ c++14 template-meta-programming boost-hana


    【解决方案1】:

    有趣的问题。

    避免使用StrConst,在下面的示例中您可以看到一种获取std::tuple&lt;std::integral_constant&lt;std::size_t, Pos1&gt;, std::integral_constant&lt;std::size_t, Pos2&gt;, ...&gt; 类型的方法。

    首先,使用arrayConverter()char const *转换成std::array&lt;char, N&gt;lc,在下面的代码中);第二:使用tupleIndexGenerator&lt;lc.size(), lc, '^'&gt;::type获取请求的类型。

    所以,如果"hello^world^" 是字符串,则从tupleIndexGenerator&lt;lc.size(), lc, '^'&gt;::type 得到std::tuple&lt;std::integral_constant&lt;std::size_t, 5U&gt;, std::integral_constant&lt;std::size_t, 11U&gt;&gt;

    代码

    #include <iostream>
    #include <array>
    #include <tuple>
    
    template <typename, typename>
    struct typeConcat;
    
    template <typename T0, template <typename ...> class C, typename ... Ts>
    struct typeConcat<T0, C<Ts...>>
     { using type = C<T0, Ts...>; };
    
    template <std::size_t N, std::array<char, N> const & A, char CH,
              std::size_t Pos = 0U, bool EQ = ((Pos < N) && (A.at(Pos) == CH))>
    struct tupleIndexGenerator;
    
    template <std::size_t N, std::array<char, N> const & A, char CH>
    struct tupleIndexGenerator<N, A, CH, N, false>
     { using type = std::tuple<>; };
    
    template <std::size_t N, std::array<char, N> const & A, char CH,
              std::size_t Pos>
    struct tupleIndexGenerator<N, A, CH, Pos, false>
     { using type = typename tupleIndexGenerator<N, A, CH, Pos+1U>::type; };
    
    template <std::size_t N, std::array<char, N> const & A, char CH,
              std::size_t Pos>
    struct tupleIndexGenerator<N, A, CH, Pos, true>
     { using type = typename typeConcat<
          std::integral_constant<std::size_t, Pos>,
          typename tupleIndexGenerator<N, A, CH, Pos+1U>::type>::type; };
    
    template <typename T, size_t N, size_t ... Is>
    constexpr auto arrayConverter (T const (&arr)[N],
                                   std::index_sequence<Is...> const &)
     { return std::array<T, N> { { arr[Is]... } }; }
    
    template <typename T, size_t N>
    constexpr auto arrayConverter (T const (&arr)[N])
     { return arrayConverter(arr, std::make_index_sequence<N>{}); }
    
    constexpr auto lc = arrayConverter("hello^world^");
    
    int main ()
     {
       static_assert(std::is_same<
                     typename tupleIndexGenerator<lc.size(), lc, '^'>::type,
                     std::tuple<std::integral_constant<std::size_t, 5U>,
                                std::integral_constant<std::size_t, 11U>>>::value,
                     "!");
     }
    

    【讨论】:

    • 我收到一个错误:"'lc' 不是类型 'const std::array&lt;bool, 13ul&gt;&amp;' 的有效模板参数,因为对象 'lc' 没有外部链接"。我正在使用gcc-5.4.1。我需要更新的编译器吗?
    • 好的,我在 Godbolt 上试过了——在 6.1 上编译得很好,而不是在 5.4 上
    • 谢谢!我已将问题上的标签更新为 c++14
    • @SteveLorimer - 我的 g++ 4.9.2 具有相同的“没有外部链接”;通常我使用clang++;顺便说一句:我开发了一个解决方案来生成带有插入符号位置的std::index_sequence;我正在尝试简化此解决方案,但是,如果您确实需要 std::tuple&lt;std::integral_constant&lt;int, value1&gt;, std::integral_constant&lt;int, value2&gt;, ...&gt; 类型的生成,那么我想我可以做到。
    • 我正在寻找一个 int const 的元组,因为我已经有一个用于另一段所需代码的元组,并且我需要逐个元素地对该元组进行操作。我不知道如何索引到 integer_sequence
    【解决方案2】:

    这是使用 Boost.Hana(宣传 C++14)的an example。它产生一个整数常量元组,这正是我们所要求的。

    #include <cstddef>
    #include <utility>
    
    #include <boost/hana.hpp>
    
    namespace hana = boost::hana;
    using namespace boost::hana::literals;
    
    template<typename Str, int... Is>
    constexpr auto unfilteredCaretsImpl(Str str, std::integer_sequence<int, Is...>) {
        return hana::make_tuple(boost::hana::if_(str[hana::int_c<Is>] == hana::char_c<'^'>, hana::just(hana::int_c<Is>), hana::nothing)...);
    }
    
    template<typename Str>
    constexpr auto unfilteredCarets(Str str) {
        return unfilteredCaretsImpl(str, std::make_integer_sequence<int, hana::length(str)>{});
    }
    
    template<typename Str>
    constexpr auto allCarets(Str str) {
        auto unfiltered = unfilteredCarets(str);
        auto filtered = hana::filter(unfiltered, [](auto opt) { return opt != hana::nothing; });
        return hana::transform(filtered, [](auto opt) { return opt.value(); });
    }
    
    int main() {
        constexpr auto carets = allCarets(BOOST_HANA_STRING("hello^world^"));
    
        static_assert(hana::length(carets) == std::size_t{2});
        static_assert(carets[0_c] == 5);
        static_assert(carets[1_c] == 11);
    }
    

    大部分工作都是为了解决每个值都是不同类型的事实。 Hana 将值编码为类型的一部分,这意味着可以在常量表达式中使用参数。例如,对于一些人工的String,Hana 字符串的类型为String&lt;'a', 'b', 'c'&gt;。这意味着当使用参数比较字符时,该字符被称为类型的一部分。这与 Scott 的字符串不同,后者在 constexpr 上已满,无法将参数提升为函数内的常量表达式。

    【讨论】:

      【解决方案3】:

      这是 the best I can do 和 Scott 的字符串:

      #include <cstddef>
      #include <stdexcept>
      
      class StrConst
      {
      public:
          template<size_t N>
          constexpr StrConst(const char (&str)[N])
              : str_(str)
              , len_(N - 1)
          {
              static_assert(N > 1, "not a string");
          }
      
          constexpr operator const char*() const
          {
              return str_;
          }
      
          constexpr size_t size() const
          {
              return len_;
          }
      
          constexpr char operator[] (size_t i) const
          {
              return i < len_ ? str_[i] : throw std::out_of_range("invalid index");
          }
      
      private:
          const char* const str_;
          const size_t      len_;
      };
      
      template<typename T, size_t MaxSize>
      class VectorConst
      {
      public:
          constexpr VectorConst() = default;
      
          constexpr size_t size() const 
          {
              return _size; 
          }
      
          constexpr void push_back(const T& value) {
              _data[_size++] = value;
          }
      
          constexpr T& operator[](size_t i) 
          {
              return _data[i];
          }
      
          constexpr const T& operator[](size_t i) const
          {
              return _data[i];
          }
      
      private:
          T _data[MaxSize]{};
          size_t _size = 0;
      };
      
      template<size_t Size>
      constexpr auto allCarets(const StrConst& str) {
          VectorConst<size_t, Size> result;
      
          for (size_t i = 0; i < str.size(); ++i) {
              if (str[i] == '^') {
                  result.push_back(i);
              }
          }
      
          return result;
      }
      
      int main() {
          constexpr StrConst s("hello^world^");
          constexpr auto carets = allCarets<s.size()>(s);
      
          static_assert(carets.size() == 2);
          static_assert(carets[0] == 5);
          static_assert(carets[1] == 11);
      }
      

      对于普通的constexpr 和未在类型中编码的值,我们会受到更多限制。我们根本无法形成整数常量的元组,因为参数中的字符串内容在常量表达式中是不可用的。相反,我制作了一个小的 constexpr 向量,我们可以像运行时一样使用它来推送我们找到的位置,但即便如此,我们也无法在编译时进行任何动态分配,因此它有一个需要的最大大小作为模板参数。使用 C++11,您也可以递归地而不是迭代地编写它,但我不确定您将如何实现 push_back。您需要复制数组并更改一个值。据我所知,您必须通过数组的初始化列表来完成它,并且基本上需要一个索引参数包,其大小基于一个不是常量表达式的变量,这是可能的(尽管我不知道 C++11),但是really complicated

      【讨论】:

      • 谢谢!我已将问题的标签更新为 c++14。此外,对于我的特定用例,可以在编译时知道向量的大小。我没有在问题中反映这一点,但也有许多可变参数,字符串中每个插入符号 1 个,因此我们可以使用 sizeof...(ts) 作为向量容量。考虑到这些需求变化,是否可以从您的 VectorConst 转到 std::tuple&lt;std::integral_constant&lt;int, Ns&gt;...&gt;
      • @SteveLorimer,如果您将位置编码为类型的一部分或在某处的模板参数中,则可以将它们转换为您想要的std::tuple。然而,VectorConst 是专门为不对其类型的数据进行编码而构建的——这种事情是 Hana 真正擅长的。因此,从理论上讲,问题末尾链接中的技术是可行的,但这需要首先遍历所有可能的元组(即使另一个答案中的真/假方法也给出了 2^strlength 很多)并选择正确的。
      猜你喜欢
      • 1970-01-01
      • 2019-09-13
      • 1970-01-01
      • 2022-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多