如何在保持 API 简单的同时加入编译时类似字符串的对象?

问题描述 投票:0回答:2

我试图在编译时连接类似字符串的对象。在这篇文章的帮助下,我想出了这样的东西:

#include <cstddef>
#include <algorithm>
#include <array>
#include <cstring>
#include <string>
#include <string_view>

template <std::size_t N>
class ConstexprChars
{
  private:
    std::array<char, N> _string;

    template <std::size_t S>
    friend class ConstexprChars;

    template <std::size_t S1, std::size_t S2>
    constexpr ConstexprChars(const std::array<char, S1> s1,
                             const std::array<char, S2> s2)
        : _string() {
        std::copy(s1.begin(), s1.end() - 1, _string.begin());
        std::copy(s2.begin(), s2.end() - 1, _string.begin() + S1 - 1);
    }

  public:
    constexpr ConstexprChars(const char (&str)[N]) : _string() {
        std::copy(&str[0], &str[0] + N, _string.begin());
    }

    constexpr ConstexprChars(const std::array<char, N> str) : _string() {
        std::copy(&str[0], &str[0] + N, _string.begin());
    }

    constexpr ConstexprChars(std::string_view str) : _string() {
        std::copy(str.data(), str.data() + N, _string.begin());
    }

    template <std::size_t S>
    constexpr auto operator+(const ConstexprChars<S> other) const {
        return ConstexprChars<N + other._string.size() - 1>(_string, other._string);
    }

    [[nodiscard]]
    constexpr auto c_str() const {
        return _string.data();
    }
};

template <std::size_t N, std::size_t... Ns>
constexpr auto cconcat_impl(const char (&first)[N], const char (&... rest)[Ns]) {
    if constexpr (!sizeof...(Ns)) { return ConstexprChars<N>{first}; }
    else { return ConstexprChars<N>{first} + cconcat_impl(rest...); }
}

template <std::size_t... Ns>
constexpr auto cconcat(const char (&... strings)[Ns]) {
    return cconcat_impl(strings...);
}

#include <iostream>

int main() {
    // this works;
    constexpr const char name[] = "Edward";
    constexpr auto joined1 = cconcat("name=", name);
    std::cout << joined1.c_str() << std::endl;

    // this does not work:
    // constexpr std::string_view value = "42"; // essentially same as constexpr const char*
    // constexpr auto joined2 = cconcat("value=", value);
    // std::cout << joined2.c_str() << std::endl;

    return 0;
}

但是,这仅适用于字符串文字和字符数组。有没有办法扩展其他编译时类似字符串对象的功能?

编辑: 正如@Oersted所建议的,实现这一目标的一种方法是添加这些重载和宏方案(为了简单起见,忽略了混合搭配的可能性):

constexpr std::size_t cptr_size(const char* cptr) {
    return std::string_view{cptr}.length() + 1;
}

template <std::size_t N>
constexpr auto make_array(const char* cptr) {
    std::array<char, N> arr;
    std::copy_n(cptr, N, arr.data());
    return arr;
}

#define MAKEARRAY(str) make_array<cptr_size(str)>(str)

template <std::size_t N, std::size_t... Ns>
constexpr auto
cconcat_impl(const std::array<char, N> first, const std::array<char, Ns>... rest) {
    if constexpr (!sizeof...(Ns)) { return ConstexprChars<N>{first}; }
    else { return ConstexprChars<N>{first} + cconcat_impl(rest...); }
}

template <std::size_t... Ns>
constexpr auto cconcat(const std::array<char, Ns>... strings) {
    return cconcat_impl(strings...);
}

那么,我们可以这样做:

int main() {
    constexpr const char* value = "42";
    constexpr auto joined2 = cconcat(MAKEARRAY("value="), MAKEARRAY(value));
    std::cout << joined2.c_str() << std::endl;

    return 0;
}

但是,这改变了 API。是否可以在保留相同 API 的同时实现这一目标?也就是说,是否可以有这个:

int main() {
    constexpr const char* value = "42";
    constexpr auto joined2 = cconcat("value=", value);
    std::cout << joined2.c_str() << std::endl;

    return 0;
}
c++ constexpr string-concatenation string-view char-pointer
2个回答
1
投票

您需要将串联结果存储在编译时大小的数组中(例如

std::array
)。但是
std::string_view::size
是其值的一部分(而不是其类型),并且不能用于确定串联函数的结果类型。最直接的方法是定义一个
fixed_string
类模板并将大小放入其类型元数据中。类模板可以将转换运算符声明为
std::array
std::string_view
std::string
。对于运行时串联,您需要在串联序列中提供至少一个
std::string
类的实例。如果使用
operator+
来执行串联,语法将保持一致:

template<std::array val>
struct fixed_string{
    using this_arr_t = std::remove_cv_t<decltype(val)>;
    using value_type = this_arr_t::value_type;
    using string_view = std::basic_string_view<value_type>;

    consteval static auto size()
    {return size(val)-!back(val);};

    consteval operator std::basic_string_view<value_type>()
    const{return string_view{data(val), this->size()};};

    constexpr operator std::basic_string<value_type>()
    const{return std::basic_string{
                 string_view{*this}};};

    consteval static auto begin()
    {return std::begin(string_view{fixed_string{}});};

    consteval static auto end()
    {return std::end(string_view{fixed_string{}});};

    template<std::size_t N>
    consteval static auto arrcat(array<value_type, N> rhs)
    ->std::array<value_type, size() + N> {
           std::array<value_type, size() + N> res;
           std::ranges::copy(rhs,
           std::ranges::copy(fixed_string{}, begin(res)).second);
           return res;
    };

    template<std::array rhs>
    consteval fixed_string<arrcat(rhs)>
    operator+(fixed_string<rhs>)
    {return {};};
private:
    // I would like the class empty 
};

template<std::array val>
consteval fixed_string<val> operator""_fxstr()
{return {};};

auto str = "this"_fxstr + " is a "_fxstr + "std::string"s;

为了清楚起见,我留下了很多实现细节。例如:我会从末尾删除空终止符,但人们可能想保留它。 但是 ` 提供了一种更简单的方法来零努力地实现这一目标:

auto str = std::array{ "this"sv
                     , "is"sv
                     , "a"sv
                     , "std::string"s }
         | std::views::join("_")
         | std::ranges::to<std::string>();

如果输入是,则

join
的结果可以是
constexpr
。但在被收集为
string
vector
ranges::to
之前,它不会是连续的范围。

最后一点 - 正如评论中已经提到的 - 是避免定义与 Qt 等著名库中的名称相同的标识符:

QObject
是与您的预期用途无关的名称,并且是抽象基类名称Qt


0
投票

总结我的评论,我实现了这个片段。是否满足您的需求:

#include <algorithm>
#include <array>
#include <cstddef>
#include <iostream>
#include <string_view>

namespace Details {
template <size_t N>
struct StringWrapper {
    std::array<char, N> value;
    std::size_t size;

    constexpr explicit(false) StringWrapper(const char (&str)[N]) : size(N) {
        std::copy_n(static_cast<const char *>(str), N, value.data());
    }
};
}  // namespace Details

template <Details::StringWrapper head, Details::StringWrapper tail>
class SConcat {
    constexpr static std::array<char, head.size + tail.size - 1> init() {
        std::array<char, head.size + tail.size - 1> conc;
        std::copy(std::cbegin(head.value), std::cend(head.value) - 1,
                  std::begin(conc));
        std::copy(std::cbegin(tail.value), std::cend(tail.value),
                  std::begin(conc) + head.size - 1);
        return conc;
    }
    static constexpr std::array<char, head.size + tail.size - 1> data = init();

   public:
    static constexpr std::string_view as_sv() { return {data.data()}; }
};

int main() {
    constexpr char value[] = "42";
    const std::string_view conc{SConcat<"value=", value>::as_sv()};
    std::cout << conc << '\n';
    return static_cast<int>(conc[1] == 'a');
}

直播

我认为它可以得到显着的完善,但它给出了主要思想。

First

StringWrapper
只是一种将原始字符串隐式转换为可用作
SConcat
的 CNTTP 对象的方法(因此需要 C++20)。

因此,

SConcat
为其声明中使用的每对字符串提供了唯一的类型,并且串联结果保存在提供存储的静态成员变量
data
中。

通过

string_view
方法最终可以得到
as_sv()

(NB1,通过删除

std::cout
部分,您可以看到编译器如何优化所有内容,然后是程序集:

main:
        mov     eax, 1
        ret

NB2

static_cast
只是为了消除一些误报警告。 )


编辑:支持各种字符串文字声明的替代方案,按照 op 的要求,有点详细。

#include <algorithm>
#include <array>
#include <cstddef>
#include <string>
#include <string_view>

// #define PROVEOPTIM
#ifndef PROVEOPTIM
#include <iostream>
#endif

namespace Details {
constexpr std::size_t MySize(const char *str) {
    std::string tmp{str};
    return tmp.length();
}
template <std::size_t N>
constexpr auto MakeArray(const char *str) {
    std::array<char, N + 1> arr;
    std::copy_n(str, N + 1, arr.data());
    return arr;
}
}  // namespace Details

#define MAKEARRAY(str) Details::MakeArray<Details::MySize(str)>(str)

template <std::array head, std::array tail>
class SConcat {
    constexpr static std::array<char, head.size() + tail.size() - 1> init() {
        std::array<char, head.size() + tail.size() - 1> conc;
        std::copy(std::cbegin(head), std::cend(head) - 1, std::begin(conc));
        std::copy(std::cbegin(tail), std::cend(tail),
                  std::begin(conc) + head.size() - 1);
        return conc;
    }
    static constexpr std::array<char, head.size() + tail.size() - 1> data =
        init();

   public:
    static constexpr std::string_view as_sv() { return {data.data()}; }
};

int main() {
    constexpr const char *value = "42";
    constexpr const char value2[] = "3.14";
    const std::string_view conc{
        SConcat<MAKEARRAY("value="), MAKEARRAY(value)>::as_sv()};
    const std::string_view conc2{
        SConcat<MAKEARRAY("value="), MAKEARRAY(value2)>::as_sv()};
#ifndef PROVEOPTIM
    std::cout << conc << '\n';
    std::cout << conc2 << '\n';
#endif
    return static_cast<int>(conc[1] == 'a');
}

直播

它依赖于从指向作为

std::array
const char*
传递的空终止字符串的指针显式构建
const char[]
。宏
MAKEARRAY
将两个操作合并为一个:获取大小作为编译时常量,并从用于获取大小的同一源复制数据。 显然,如果“字符串”不是以 null 结尾,则结果是未定义的。

© www.soinside.com 2019 - 2024. All rights reserved.