用于创建 integral_constants 的任意元组的通用实用程序

Generic utility to create aribtrary tuples of integral_constants

利用 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 函数,它 returns 在字符串中找到的第一个插入符号的位置,从位置 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

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

#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 将值编码为类型的一部分,这意味着可以在常量表达式中使用参数。例如,一个 Hana 字符串是 String<'a', 'b', 'c'> 类型的 String。这意味着当使用参数比较字符时,该字符被称为类型的一部分。这与 Scott 的字符串不同,后者在 constexpr 上充满并且不能将参数提升为函数内的常量表达式。

这是 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.

有趣的问题。

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

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

所以,如果 "hello^world^" 是字符串,从 tupleIndexGenerator<lc.size(), lc, '^'>::type 你会得到 std::tuple<std::integral_constant<std::size_t, 5U>, std::integral_constant<std::size_t, 11U>>

密码

#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,
                 "!");
 }