C++ 指针可以指向字符串文字的静态成员数组吗?

Can a C++ pointer point to a static member array of string literals?

2017 年 Visual Studio 一切正常,但我在 GCC (6.5.0) 中遇到链接器错误。

下面是一些可以解决我的问题的示例代码:

#include <iostream>

struct Foo{
    static constexpr const char* s[] = {"one","two","three"};
};

int main(){
    std::cout << Foo::s[0] << std::endl;  //this works in both compilers
    const char* const* str_ptr = nullptr;
    str_ptr = Foo::s;                     //LINKER ERROR in GCC; works in VS
    std::cout << str_ptr[1] << std::endl; //works in VS
    return 0;
}

在 GCC 中,我得到 undefined reference to 'Foo::s'。我需要 Foo::s 的初始化保留在结构的声明中,这就是我使用 constexpr 的原因。有没有办法动态引用 Foo::s,即使用指针?

更多背景信息

我现在将解释为什么我想这样做。我正在开发嵌入式软件来控制设备的配置。软件加载包含参数名称及其值的配置文件。要配置的参数集在编译时确定,但需要模块化,以便随着设备的继续开发轻松添加新参数并扩展它们。换句话说,它们的定义需要在代码库中的一个地方。

我的实际代码库有数千行,可以在 Visual Studio 中运行,但这里有一个简化的玩具示例:

#include <iostream>
#include <string>
#include <vector>

//A struct for an arbitrary parameter
struct Parameter {
    std::string paramName;
    int max_value;
    int value;
    const char* const* str_ptr = nullptr;
};

//Structure of parameters - MUST BE DEFINED IN ONE PLACE
struct Param_FavoriteIceCream {
    static constexpr const char* n = "FavoriteIceCream";
    enum { vanilla, chocolate, strawberry, NUM_MAX };
    static constexpr const char* s[] = { "vanilla","chocolate","strawberry" };
};
struct Param_FavoriteFruit {
    static constexpr const char* n = "FavoriteFruit";
    enum { apple, banana, grape, mango, peach, NUM_MAX };
    static constexpr const char* s[] = { "apple","banana","grape","mango","peach" };
};

int main() {
    //Set of parameters - determined at compile-time
    std::vector<Parameter> params;
    params.resize(2);

    //Configure these parameters objects - determined at compile-time
    params[0].paramName = Param_FavoriteIceCream::n;
    params[0].max_value = Param_FavoriteIceCream::NUM_MAX;
    params[0].str_ptr = Param_FavoriteIceCream::s; //!!!! LINKER ERROR IN GCC !!!!!!

    params[1].paramName = Param_FavoriteFruit::n;
    params[1].max_value = Param_FavoriteFruit::NUM_MAX;
    params[1].str_ptr = Param_FavoriteFruit::s; //!!!! LINKER ERROR IN GCC !!!!!!

    //Set values by parsing files - determined at run-time
    std::string param_string = "FavoriteFruit"; //this would be loaded from a file
    std::string param_value = "grape"; //this would be loaded from a file
    for (size_t i = 0; i < params.size(); i++) {
        for (size_t j = 0; j < params[i].max_value; j++) {
            if (params[i].paramName == param_string
            && params[i].str_ptr[j] == param_value) {
                params[i].value = j;
                break;
            }
        }
    }
    return 0;
}

如您所见,涉及枚举和字符串数组,它们需要匹配,因此出于维护目的,我需要将它们保存在同一个位置。此外,由于此代码已经编写并将在 Windows 和 Linux 环境中使用,因此修复越小越好。我宁愿不必为了让它在 Linux 中编译而重写数千行。谢谢!

看来您没有使用 C++17,而在 C++17 之前,这是未定义的行为,我非常不喜欢这种行为。你没有为你的s定义,但你是ODR-using它,这意味着你需要有一个定义。

要定义它,您必须在 .cpp 文件中定义它。

在 C++17 中,这将是有效代码。我不熟悉 MSVC,所以我不确定为什么它在那里工作得很好——是因为它被编译为 C++17,还是因为它只是未定义行为的不同表现形式。

该程序在 C++17 中有效。该程序在 C++14 或更早的标准中无效。 GCC 6.5.0默认的标准模式是C++14.

为了使程序符合C++14,您必须定义静态成员(恰好在一个翻译单元中)。自 C++17 起,constexpr 声明隐含为内联变量定义,因此不需要单独定义。

解决方案 1:升级您的编译器并使用具有内联变量的 C++17 标准(或更高版本,如果您来自未来)。自 GCC 7 以来已经实现了内联变量。

解决方案2:在class定义之外的变量定义在一个翻译单元中(初始化保留在声明中)。

对于 C++98、C++11 和 C++14,您需要显式地告诉编译器 Foo::s 的初始化位置(见下文),您就可以开始了。

struct Foo{
    static const char* s[];
};
const char* Foo::s[] = {"one","two","three"};

正如其中一条评论中所解释的那样,您的初始化从 C++17 就可以了。