【问题标题】:C++11 type to enum mapping?C ++ 11类型到枚举映射?
【发布时间】:2013-02-27 13:35:42
【问题描述】:

我有一个像这样的枚举:

enum E
{
    TYPE_FLOAT,
    TYPE_CHAR,
    TYPE_INT
}

我想创建一个编译时映射来为以下类型获取适当的 E:

GetE<float> // returns TYPE_FLOAT
GetE<char> // returns TYPE_CHAR
GetE<int> // returns TYPE_INT

我想到了:

template<class T> struct GetE;

template<> struct GetE<float> { static constexpr E type = TYPE_FLOAT; };
template<> struct GetE<char> { static constexpr E type = TYPE_CHAR; };
template<> struct GetE<int> { static constexpr E type = TYPE_INT; };

但我收到如下错误:

undefined reference to `GetE<int>::type'

最好的方法是什么?为什么会出错?

【问题讨论】:

    标签: c++ c++11 constexpr


    【解决方案1】:

    这取决于你如何使用这些常量表达式。

    ODR(单一定义规则)指出

    (§3.2/2) [...] 名称显示为潜在求值表达式的变量是 odr-used 除非它是满足出现在常量表达式 (5.19) 和左值中的要求的对象立即应用右值转换 (4.1)。 [...]

    (然后,许多特殊的规则,例外和例外的例外。)

    odr 使用的任何变量都必须有一个定义。您的常量表达式有一个声明,但没有一个定义,所以这很顺利,除非您使用其中一个。

    例如,下面的很顺利:

    int main() {
      E e = GetE<float>::type;
      return 0;
    }
    

    但这不是:

    void f(const E &)
    { }
    
    int main() {
      f(GetE<float>::type);
      return 0;
    }
    

    因为f 需要一个 (const) 引用,所以左值到右值的转换不能立即应用,因此这构成了 odr 使用。编译器会抱怨它错过了一个定义。

    (备注。正如 ShafikYaghmour 发现的(参见 cmets),如果编译器使用优化,您可能不会收到投诉,因为引用可能会被优化掉。要重现编译器投诉,请使用 -O0 标志(或类似标志) ,取决于编译器)。)

    为了解决这个问题,可以以通常的方式提供所需的定义,即在结构定义之外:

    constexpr E GetE<float>::type;
    constexpr E GetE<char>::type;
    constexpr E GetE<int>::type;
    

    但由于这必须发生在 .cpp(而不是头文件)中,您最终将不得不在两个不同的地方维护声明和定义,这很麻烦。

    您刚刚在评论中建议的解决方案,即定义一个 constexpr(和内联)函数,听起来不错:

    template <class T> constexpr E GetE();
    
    template <> constexpr E GetE<float>()
    { return TYPE_FLOAT; }
    
    template <> constexpr E GetE<char>()
    { return TYPE_CHAR; }
    
    template <> constexpr E GetE<int>()
    { return TYPE_INT; }
    
    void f(const E &)
    { }
    
    int main() {
      E e = GetE<float>();
    
      f(GetE<float>());
    
      return 0;
    }
    

    【讨论】:

    • 是的,这听起来很合理。我已更改为使用 tmpl&lt;class A&gt; E GetE() 之类的函数模板,然后对其进行专门化,这已修复它。谢谢。
    • @jogojapan 我正在尝试从您的评论中学习,但使用最后一段代码我没有看到错误:liveworkspace.org/code/4oTEis 我错过了什么?谢谢
    • @ShafikYaghmour 这是因为 -O2 编译器标志。它优化了引用。好的评论,不过,我会在答案中提到这一点。
    • @jogojapan 谢谢,您的两个回复都在标记上,要么删除 -O2 要么在函数体中使用 e 会导致错误出现。
    【解决方案2】:

    静态成员变量需要在类范围之外定义

    class C {
        const static int x = 5;
    };
    
    decltype(C::x) C::x;
    

    【讨论】:

    • 即使是constexpr?这很奇怪。
    【解决方案3】:

    可能是因为您忘记在枚举定义后加分号,这在 LiveWorkSpace 中对我有用:

    #include <iostream>
    
    enum E
    {
       TYPE_FLOAT,
       TYPE_CHAR,
       TYPE_INT
    } ;
    
    template<class T> struct GetE;
    
    template<> struct GetE<float> { static constexpr E type = TYPE_FLOAT; };
    template<> struct GetE<char> { static constexpr E type = TYPE_CHAR; };
    template<> struct GetE<int> { static constexpr E type = TYPE_INT; };
    
    int main()
    {
        std::cout << GetE<int>::type << std::endl ;
    }
    

    这里是代码http://liveworkspace.org/code/nHqUe$6的链接

    【讨论】:

    • 在实际代码中没有我有分号。嗯很奇怪。我也在使用 4.7.2。不知道发生了什么。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2018-08-01
    • 2019-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多