【问题标题】:Is It Valid To Return Forward Declared Enum Class? (Visual Studio 2015 Linker Error)返回前向声明的枚举类是否有效? (Visual Studio 2015 链接器错误)
【发布时间】:2016-06-30 03:06:23
【问题描述】:

我遇到以下代码在 GCC(4.8+ 测试)和 Clang(3.4+ 测试)上编译但在 Visual Studio 2015 (VC++ 14.0) 上无法编译的问题。

Foo.h:

#include <functional>

namespace Error {
enum class Code;
static const Code None = static_cast<Code>(0);
}

class Foo{
public:
  std::function<Error::Code()> Run();
};

Foo.cpp

#include "Foo.h"
#include <iostream>

std::function<Error::Code()> Foo::Run() {
  return [&]() {
    std::cout << "hello\n"; 
    return Error::None;
  };
}

main.cpp:

#include "Foo.h"

namespace Error {
enum class Code {
  None = 0,
  Error = 1,
};
}

int main() {
  Foo foo;
  foo.Run()();
}

VC++ 14.0 中产生的错误如下:

Foo.obj : error LNK2001: unresolved external symbol "enum Error::Code __cdecl std::_Invoke_ret<enum Error::Code,class <lambda_813e82254384ef384f6a5fe34e885f01> &>(struct std::_Forced<enum Error::Code,0>,class <lambda_813e82254384ef384f6a5fe34e885f01> &)" (??$_Invoke_ret@W4Code@Error@@AAV<lambda_813e82254384ef384f6a5fe34e885f01>@@@std@@YA?AW4Code@Error@@U?$_Forced@W4Code@Error@@$0A@@0@AAV<lambda_813e82254384ef384f6a5fe34e885f01>@@@Z)

我认为这是一个用于实现 std::function 的内部标准库函数。

此代码类似于我尝试使用的内部库的使用,它共享工具的标准程序接口,但前向声明错误代码以便可以自定义它们。我相信这应该是基于 §7.2 的有效代码(请参阅this answer)枚举虽然是前向声明的,但应该是一个完整的类型并且可用作返回值。这是标准中的相关位:

一个 opaque-enum-declaration 要么是在当前范围内重新声明一个枚举,要么是一个新枚举的声明。 [注意:由 opaque-enum-declaration 声明的枚举具有固定的基础类型并且是完整的类型。枚举器列表可以在稍后的重新声明中使用枚举说明符提供。 ——尾注]

此代码有效吗?如果是这样,是否有办法让 VC++ 接受它?

【问题讨论】:

  • “枚举虽然是前向声明的,但应该是一个完整的类型并且可以用作返回值。”前向声明不提供完整的类型 枚举与否。
  • @πάνταῥεῖ 这就是为什么这在技术上不是一个前向声明而是一个不透明的枚举声明(如果“前向声明”这个术语有一个正确的定义......)无论如何,这样的声明引入了一个完整的枚举类型。枚举有一个特殊情况,IIRC 永远不会引入不完整的枚举类型。
  • 您的enum class Code 定义 与您的声明的 enum class Error::Code 不在同一个命名空间中
  • 我认为从您的引用中可以清楚地看出代码是正确的,因此它一定是一个 VS 错误。建议提交 VS 错误报告并删除 language-lawyer 标签
  • 我刚刚在 VS2015U1 上解决了这个问题。如果您使用正确的类而不是 lambda,它仍然会出现,明确指定 lambda 上的返回类型,使用 return {} 而不是 return Error::None,将所有内容移动到一个源文件中。正如 Sam Cristall 所注意到的那样,关键点是缺少枚举的定义(即使是没有枚举数的定义也足够了)。一旦该定义存在,就不会发生链接器问题。我同意@M.M 这是一个 VS 错误。

标签: c++ visual-studio-2015 language-lawyer c++14


【解决方案1】:

是的,代码有效。

这确实看起来是 MSVC 错误。我可以用一个简单的代码示例来重现它;

func.cpp

#include <functional>
enum Code : int;
Code func2();
void func()
{
    std::function<Code()> f2 { func2 };
}

main.cpp

enum Code : int {
    Some = 0,
    Error = 1,
};
Code func2() { return Some; }
int main() {}

错误仍然存​​在;

错误 LNK2019:未解析的外部符号“枚举代码 __cdecl std::_Invoke_ret(struct std::_Forced,enum Code (__cdecl*&)(void))”(??$_Invoke_ret@W4Code@@AEAP6A?AW41@XZ @std@@YA?AW4Code@@U?$_Forced@W4Code@@$0A@@0@AEAP6A?AW41@XZ@Z) 在函数“private: virtual enum Code __cdecl std::_Func_impl,enum Code>: :_Do_call(void)" (?_Do_call@?$_Func_impl@P6A?AW4Code@@XZV?$allocator@H@std@@W41@$$V@std@@EEAA?AW4Code@@XZ)

该错误提示 std::function&lt;Code()&gt; 存在实例化问题,但任一翻译单元中的显式实例化都无法提供任何解决方案。

该错误似乎也不依赖于无作用域的enum 与作用域的enum class

目前唯一的解决办法似乎是根本不使用不透明的枚举声明,而是提供完整的枚举及其枚举数。


来自 Microsoft Connect (2016-05-09);

已将针对此问题的修复程序签入编译器源代码。该修复程序应该会出现在 Visual C++ 的未来版本中。

【讨论】:

  • 谢谢。我发现作为一种解决方法,您还可以向只能看到不透明声明的翻译单元提供“错误”的枚举定义。不过,这显然是一个 hack。
  • @SamCristall :这不是黑客行为,而是违反 ODR。即,这不是解决方法,而是 UB。
  • @ildjarn 我不认为 hack 是一个定义非常明确的术语!但是谢谢你的警告。它似乎适用于我需要的东西,幸运的是不适用于生产代码。
【解决方案2】:

我认为你应该在头文件(.h)中声明 None 并在源文件(.cpp)中定义它

Foo.h

namespace Error {
    extern const Code None;
}

Foo.cpp

namespace Error {
    const Code None = static_cast<Code>(0);
}

有时枚举会被优化并且没有实例或地址,尤其是您将其声明为静态变量。

【讨论】:

    【解决方案3】:

    这里还有一些意见太大而无法评论:

    这确实是一个编译器错误,而不是标准库实现的错误。以下程序在不使用 StdLib 的情况下在 VS2015 Update 1 上重现了相同的问题:

    template<class T>
    T create() {
        return {};
    }
    
    enum class Code;
    
    int main() {
        create<Code>();
    }
    

    链接器抱怨一个未解析的符号:

    enum Code __cdecl create&lt;enum Code&gt;(void)

    • 如果省略返回值,链接器问题就会消失(将 T 替换为 void)。
    • 显式指定基础类型时问题仍然存在。
    • 如果我们将作用域枚举替换为无作用域枚举,问题仍然存在。使用 Microsoft 的 C++ 扩展 (/Ze) 时,我们不必指定基础类型。指定基础类型时没有任何变化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-21
      • 2011-11-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多