constexpr 变量中捕获的内联数组在运行时丢失

Captured in-line array in constexpr variable gets lost on runtime

我正在尝试创建一个 [int/enum] 到文本的映射 class,并尽可能减少用户的开销。它的构造函数应该传递一个值到文本映射的列表,之后可以查询。创建的对象应该是 constexpr 并且有一个可选的大小参数,这允许编译器在编译时有选择地检查传递的映射数量是否与预期相匹配。当与枚举一起使用时,这作为一种额外的安全措施特别有用——也就是说:如果您忽略为新添加的枚举值添加映射,则可以通过这种方式强制出现编译错误。它应该在 Visual Studio 2019 和 Xcode 9 和 12 下与 C++14 一起工作。

我目前的尝试是下面的代码。但是,至少在 Visual Studio 2019 下,传递的映射数组未在 m_mappings 成员变量中正确捕获。当您 运行 此代码时, m_mappings 指向一个随机内存地址,因此您得到的任何输出都是错误的(如果它没有完全崩溃)。

#include <iostream>


template <typename Type>
struct Mapping {
    Type value;
    const char* text;
};


template <typename Type, Type maxValue = Type(-1)>
class Mapper {
public:
    template <size_t numMappings>
    explicit constexpr Mapper(const Mapping<Type>(&mappings)[numMappings]) :
        m_mappings(mappings),
        m_numMappings(numMappings) {
        static_assert(
            int(maxValue) == -1 || numMappings == int(maxValue) + 1,
            "Some mappings are missing!"
        );
    }

    const char* Map(Type value) const {
        for (size_t mappingNr = 0; mappingNr < m_numMappings; ++mappingNr)
            if (m_mappings[mappingNr].value == value)
                return m_mappings[mappingNr].text;

        return "?";
    }

private:
    const Mapping<Type>* m_mappings;
    const size_t m_numMappings;
};


enum class TestEnum {
    a,
    b,
    c,

    maxValue = c
};


int main() {
    constexpr Mapper<int> intMapper_noCheck({
        { 11, "a" },
        { 5, "b" },
        { 26, "x" }
    });
    std::cout << intMapper_noCheck.Map(10);

    constexpr Mapper<int, 3> intMapper_check({
        { 0, "z" },
        { 1, "f" },
        { 2, "t" },
        { 3, "#" }
    });
    std::cout << intMapper_check.Map(2);

    // if we'd pass e.g. TestEnum::b here, we get a nice compile time error
    constexpr Mapper<TestEnum, TestEnum::maxValue> enumMapper({
        { TestEnum::a, "-" },
        { TestEnum::b, "-" },
        { TestEnum::c, "+" }
    });
    std::cout << enumMapper.Map(TestEnum::b);

    // expected output by now: "?t-"

    std::cin.get();
    return 0;
}

一种可能的解决方案是在单独的 constexpr 变量中捕获映射数组,并将其传递给映射器对象,如下所示:

    constexpr Mapping<TestEnum> enumMapping[] = {
        { TestEnum::a, "-" },
        { TestEnum::b, "-" },
        { TestEnum::c, "+" }
    };
    constexpr Mapper<TestEnum, TestEnum::maxValue> enumMapper(enumMapping);
    std::cout << enumMapper.Map(TestEnum::b);

这样映射确实得到保留并且输出是正确的。但是,我发现这个额外的层使它变得更加 'messy'...

这里的复杂因素是传入的数组大小必须以 constexpr 友好的方式捕获,我不想手动单独指定它。

还有什么选择可以像我的第一个版本一样保持美观和干净?

它失败的原因是您存储了一个指向传递给构造函数的数组的指针供以后使用。

但在您的紧凑型案例中,在构造函数具有 运行 之后该数组不再存在。所以你需要做一些分配(在所有编译器中可能不是 constexpr-friendly)和复制。您使用 char* 而不是 std::string 的事实进一步加剧了这个问题 - 也不能保证 pointed-to 字符串在您使用时仍然存在。

此外,除非这是为了某种课程作业,否则请考虑查看 https://github.com/serge-sans-paille/frozen

提供的集合和映射的 constexpr 实现

经过更多的篡改后,我想出了一个解决方案,我只是将传递的映射复制到一个普通的旧数组成员中。我也尝试使用 std::array,但它在 C++14 中 constexpr-friendly 还不够。

我之前尝试的是在模板化构造函数中自动捕获映射列表大小(numMappings由编译器推断),然后将其匹配到指定的期望映射数从class 模板 (maxValue)。但是现在 class 模板本身需要知道我们要传递的映射数量,以便它可以为副本保留存储空间。因此,我还重新调整了 maxValue 参数的用途以准确表示这一点。

因此,一个缺点是您现在总是需要手动计算要传递的映射数量,这在映射到您真正不想关心的大的不连续 int 范围时会很痛苦细节。对于枚举,虽然没有什么真正的改变,但我写这个 class 主要是为了处理枚举。

所以这不是 100% 完美地符合问题,但我想它会……啊好吧。

#include <iostream>


template <typename Type>
struct Mapping {
    Type value;
    const char* text;
};


template <typename Type, Type maxValue>
class Mapper {
public:
    template <size_t numMappings>
    explicit constexpr Mapper(const Mapping<Type>(&mappings)[numMappings]) :
        m_mappings{} {
        constexpr int correctNumMappings{ int(maxValue) + 1 };

        static_assert(numMappings <= correctNumMappings, "Too many mappings given!");
        static_assert(numMappings >= correctNumMappings, "Some mappings are missing!");

        for (size_t mappingNr = 0; mappingNr < numMappings; ++mappingNr)
            m_mappings[mappingNr] = mappings[mappingNr];
    }

    const char* Map(Type value) const {
        for (const Mapping<Type>& mapping : m_mappings)
            if (mapping.value == value)
                return mapping.text;

        return "?";
    }

private:
    Mapping<Type> m_mappings[int(maxValue) + 1];
};


enum class TestEnum {
    a,
    b,
    c,

    maxValue = c
};


int main() {
    constexpr Mapper<int, 3> intMapper_check({
        { 0, "z" },
        { 1, "f" },
        { 2, "t" },
        { 3, "#" }
    });
    std::cout << intMapper_check.Map(2) << "\n";

    constexpr Mapper<TestEnum, TestEnum::maxValue> enumMapper({
        { TestEnum::a, "-" },
        { TestEnum::b, "-" },
        { TestEnum::c, "+" }
    });
    std::cout << enumMapper.Map(TestEnum::b) << "\n";

    std::cin.get();
    return 0;
}