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

时间:2017-04-09 22:16:07

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

利用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::tuple std::integral_constants创建一个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>...>;

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

Working example

3 个答案:

答案 0 :(得分:1)

Here's an example using Boost.Hana (which advertises C++14). It results in a tuple of integral constants, which is directly what was asked for.

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

Most of the work is getting around the fact that each value is a different type. Hana encodes the values as part of the type, which means it's possible to use a parameter in a constant expression. For example, a Hana string is of type String<'a', 'b', 'c'> for some artificial String. That means that when using a parameter to compare a character, that character is known as part of the type. This is different from Scott's string, which goes full on constexpr and cannot lift a parameter to a constant expression within the function.

答案 1 :(得分:1)

Here's the best I can do with Scott's string:

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

With ordinary constexpr and values not being encoded in types, we're a bit more limited. We simply can't form a tuple of integral constants because the string content in the parameter is unusable in constant expressions. Instead, I made a small constexpr vector that we can use just like a runtime one to push locations as we find them, but even then, we can't do any dynamic allocations at compile-time, so it has a max size that needs to be a template parameter. With C++11, you can possibly also write this recursively instead of iteratively, but I'm not sure how you'd implement push_back. You need to copy the array and change a value. As far as I know, you'd have to do it through an initializer list for the array and would essentially need a parameter pack of indices whose size is based on a variable that isn't a constant expression, which is possible (though I don't know about C++11), but really complicated.

答案 2 :(得分:1)

有趣的问题。

避免使用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,
                 "!");
 }