【问题标题】:How to create a constexpr std::vector<std::string> or something similar?如何创建 constexpr std::vector<std::string> 或类似的东西?
【发布时间】:2020-09-09 17:51:58
【问题描述】:

所以我一直在环顾四周并尝试不同的事情,但我无法理解如何使用 constexpr 创建一些字符串集合。

我要做的基本上是以下内容,显然无法编译:

constexpr std::vector<std::string> fizzbuzz(){
    size_t N = 100;

    std::vector<std::string> result;
    result.reserve(N);

    for (int i = 0; i < N; i++){
        int k = i+1;

        if(k % 5 == 0 && k % 3 == 0){
            result.push_back("FizzBuzz");
        }
        else if(k % 5 == 0){
            result.push_back("Buzz");
        }
        else if(k % 3 == 0){
            result.push_back("Fizz");
        }
        else{
            result.push_back(std::to_string(k));
        }

    }
    return result;
}

如果我知道如何做一些简单的事情,我已经很高兴了:

constexpr std::string fizzbuzz(int k){
    if(k % 3 == 0) return "Fizz";
    else return std::to_string(k);
}

我认为这只是完成解决方案的一小步。 它不必是 std::strings 也不必是 std::vectors。

哦,C++ 标准越低越好。

已编辑以帮助更好地理解问题。

【问题讨论】:

  • 你可以在 c++20 中做到这一点。
  • std::vector/std::string 没有 constexpr 构造函数(在 C++20 之前)...std::arraystd::string_view 有。
  • @cigien:据我所知,即使在 C++20 中,OP 也无法打印(运行时)一个 constexpr 容器(应该保留在 constexpr 评估中)。
  • @Jarod42 是的,没错。
  • 哦,不知怎的,我错过了问题的重点,我的错

标签: c++ constexpr


【解决方案1】:

std::vector/std::string 在 C++20 之前没有 constexpr 构造函数... 即使在 C++20 中,constexpr 分配也不应该从 constexpr 评估中逃脱,因此不能在运行时使用(用于打印)。

我没有看到将整数转换为 char 序列表示的标准 constexpr 方法。 std::to_stringstd::to_charsstd::format 不是constexpr

因此,您可以使用std::tuple,而不是同类容器,例如 (C++17):

template <std::size_t I>
constexpr auto fizzbuzz_elem()
{
    if constexpr (I % 5 == 0 && I % 3 == 0) {
        return "FizzBuzz";
    } else if constexpr (I % 5 == 0) {
        return "Buzz";
    } else if constexpr (I % 3 == 0){
        return "Fizz";
    } else {
        return I;
    }
}

template <std::size_t...Is>
constexpr auto fizzbuzz_impl(std::index_sequence<Is...>){
    return std::make_tuple(fizzbuzz_elem<1 + Is>()...);
}

template <std::size_t N>
constexpr auto fizzbuzz(){
    return fizzbuzz_impl(std::make_index_sequence<N>());
}

int main() {
    constexpr auto res = fizzbuzz<42>();
    std::apply([](auto... e){ ((std::cout << e << std::endl), ...); }, res);
}

Demo

【讨论】:

    【解决方案2】:

    做这种事情的一种方法是使用frozen 库,它在 C++14 中工作,它的一部分在 C++11 中工作。 (我们在一些 C++11 代码的生产环境中使用它。)

    图书馆提供了几件事来实现constexpr

    虽然std::string 最终可以调用动态内存分配器,但这对constexpr 不友好(除非他们在我错过的最新标准中取得了重大进展?)frozen::string 基本上是一个指向字符串常量。因此,如果您的数据结构正在 constexpr 初始化,frozen::string 永远不会进行分配,这就是它可以对 constexpr 友好的原因。

    冻结容器的 API 与 C++ 标准库容器非常相似,但它们在构建后不可修改。此外,它们在运行时非常高效——映射基于在编译时创建完美的哈希表,并且它们也不进行任何动态内存分配。

    这是一个例子:

    #include <frozen/unordered_map.h>
    #include <frozen/string.h>
    
    constexpr frozen::unordered_map<frozen::string, int, 2> olaf = {
        {"19", 19},
        {"31", 31},
    };
    constexpr auto val = olaf.at("19");
    

    如果您有一堆字符串常量需要映射到您的软件的配置值,这将非常有用,反之亦然。

    const 在文件范围内被 constexpr 初始化的变量自 C++11 以来没有传统的静态初始化。这意味着,在进入 main 之前不会调用它们的构造函数,没有“静态初始化顺序惨败”。相反,它们最终会出现在可执行文件的 BSS 只读内存段中,并且已经存在正确的值。如果你有很多这样的映射或者它们很大,这可以显着提高应用程序的启动时间,因为在进入 main 之前没有调用 malloc 和复制大量字符串。

    https://github.com/serge-sans-paille/frozen

    【讨论】:

    • 您能以编程方式创建地图吗?主要问题可能是“int to string”部分。
    • 我认为,如果您可以通过编程方式制作地图中的 std::array 对,那么您可以通过编程方式制作地图 - 如果您不能这样做,它将无法工作。
    • 我记得堆栈上有一个cvector 类型,它是一个constexpr 向量,具有预定的最大容量,我们用来实现完美的散列部分。我不记得这是否是公共 api 的一部分
    • 这件事看起来与 OP 的帖子相关:github.com/serge-sans-paille/frozen/blob/master/include/frozen/…
    【解决方案3】:

    std::vector 和 std::to_string() 不是 constexpr。 您的第二个函数示例将在没有这些的情况下使用 std::string_view,例如:

    constexpr std::string_view fizzbuzz(const int k){ if(k % 3 == 0) return "Fizz"; else return "Buzz"; }
    

    我相信 std::string_view 是 c++17

    【讨论】:

    • 嘿,谢谢,但我不想在 else 中返回“Buzz”,而是在 k 的 to_string 版本中返回
    【解决方案4】:

    所以在@Jarod42 的帮助下我终于解决了:

    请参阅 cmets 了解为什么要删除它。

    template <int N>
    constexpr std::array<char[9], N> solution8(){
        std::array<char[9], N> result{};
    
        for(int i = 0; i < N; i++){
            int k = i + 1;
            if ((k % 3 == 0) && (k % 5 == 0)){
                sprintf(result[i], "FizzBuzz\0");
            }
            else if (k % 3 == 0){
                sprintf(result[i], "Fizz\0");
            }
            else if (k % 5 == 0){
                sprintf(result[i], "Buzz\0");
            }
            else {
                sprintf(result[i], "%d\0", i+1);
            }
        }
    
        return result;
    }
    

    【讨论】:

    • 我不认为sprintf 是 constexpr
    • @GuillaumeRacicot 它确实可以编译,这还不够证明吗?
    • 没有。编译器具有扩展、特定于实现的行为和其他可以使代码不可移植的东西。其实none of the three major compiler is able to compile your code.
    • @GuillaumeRacicot 哦,谢谢,我没有在主目录中添加“constexpr”。删除它编译 - 我想我应该阅读更多关于 constexpr 的内容。感谢您的评论。
    猜你喜欢
    • 1970-01-01
    • 2021-11-19
    • 2021-08-13
    • 1970-01-01
    • 2015-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多