3

I have an issue where the following code compiles on GCC (4.8+ tested) and Clang (3.4+ tested) but does not compile on 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()();
}

The resulting error in VC++ 14.0 is as follows:

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)

Which I believe is an internal std library function for implementng std::function.

This code is similar to usage of an internal library I'm trying to use that shares a standard program interface for tools, but forward declares Error Codes so they can be customized. I believe this should be valid code based on §7.2 (see this answer) The enum, though forward declared, should be a complete type and usable as a return value. Here is the relevant bit in the standard:

An opaque-enum-declaration is either a redeclaration of an enumeration in the current scope or a declaration of a new enumeration. [Note: An enumeration declared by an opaque-enum-declaration has fixed underlying type and is a complete type. The list of enumerators can be provided in a later redeclaration with an enum-specifier. —end note ]

Is this code valid? If so, is there a work around to get VC++ to accept it?

Community
  • 1
  • 1
Sam Cristall
  • 4,328
  • 17
  • 29
  • 4
    _"The enum, though forward declared, should be a complete type and usable as a return value."_ Forward declarations don't provide _complete types_ enum or not. – πάντα ῥεῖ Mar 14 '16 at 21:25
  • 5
    @πάνταῥεῖ That's why this is technically not a forward-declaration but an opaque-enum-declaration (if there even is a proper definition of the term "forward-declaration"...) In any case, such a declaration introduces *a complete enumeration type*. There's a special case for enumerations, IIRC to never introduce incomplete enumeration types. – dyp Mar 14 '16 at 21:29
  • your `enum class Code` _definition_ is not in the same namespace as your _declared_ `enum class Error::Code` – inetknght Mar 14 '16 at 21:30
  • What happens if you define and use proper enumerators within the lambda, instead of `None`? Did you compile with `/Za`? Did you try to explicitly specify the underlying type? – dyp Mar 14 '16 at 21:33
  • @inetknght Sorry, that was a re-typing mistake for the minimal example, putting it in the namespace does not fix the issue. – Sam Cristall Mar 14 '16 at 21:47
  • @πάνταῥεῖ In the answer I linked, the standard would appear to say otherwise (referring to opaque-enum-declaration). I've added the relevant standard quote to my question. – Sam Cristall Mar 14 '16 at 21:49
  • @dyp If I define the enum within the same translation unit as the lambda, it appears to work, even if its definition conflicts with that of the real implementation. This will function as a work around, so thanks for the idea! Though I'm still curious if this is a true bug in Visual Studio. – Sam Cristall Mar 14 '16 at 21:54
  • 3
    I think it's clear from your quote that the code is correct and therefore it must be a VS bug. Suggest filing a VS bug report and removing language-lawyer tag – M.M Mar 14 '16 at 22:37
  • Code encapsulation question: Why are you implementing Error::Code in main.cpp? It's obviously part of Foo.h/cpp - and Foo.cpp has no direct visibility into main.cpp. It seems odd. It would be like having a socket class and defining all the error codes near your main() entry point & not in the socket class files. – Jason De Arte Mar 15 '16 at 00:39
  • @JasonDeArte The actual library helps implement some callback signals, so the enumeration is for maintaining a consistent callback signature. – Sam Cristall Mar 15 '16 at 07:40
  • 2
    I've just played around a bit with this issue on VS2015U1. It still appears if you use a proper class instead of the lambda, explicitly specifying the return type on the lambda, using `return {}` instead of `return Error::None`, moving everything into one source file. The crucial point, as Sam Cristall has noticed, is the lack of a definition of the enumeration (even a definition with no enumerators is sufficient). Once that definition is there, no linker issue occurs. I agree with @M.M this is a VS bug. – dyp Mar 15 '16 at 12:10

3 Answers3

3

Yes, the code is valid.

This certainly does appear to be MSVC bug. I am able to reproduce this with a simple code sample;

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() {}

The error remains;

error LNK2019: unresolved external symbol "enum Code __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) referenced in function "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)

The error hints at an instantiation issue with std::function<Code()>, but no amount of explicit instantiation in either translation unit offers any resolution.

The error also does not seem dependent on the un-scoped enum vs. the scoped enum class.

The only work around at this time appears to be not using an opaque enum declaration at all, but to supply the full enum with its enumerators.


From Microsoft Connect (2016-05-09);

A fix for this issue has been checked into the compiler sources. The fix should show up in the future release of Visual C++.

Niall
  • 30,036
  • 10
  • 99
  • 142
  • 1
    Thanks. I found as a work around you can also supply a "wrong" enum definition to the translation units that can only see the opaque declaration. This is obviously a hack though. – Sam Cristall Mar 15 '16 at 13:57
  • @SamCristall : That's not a hack, that's an ODR violation. I.e., it's not a workaround, it's UB. – ildjarn Mar 21 '16 at 01:49
  • @ildjarn I don't think hack is a very well defined term! But thank you for the warning. It appears to be working for what I need and fortunately isn't for production code. – Sam Cristall Mar 21 '16 at 16:37
1

Here are some more observations too large for a comment:

This is indeed a compiler bug, not a bug of the Standard Library implementation. The following program reproduces the same issue on VS2015 Update 1 without using the StdLib:

template<class T>
T create() {
    return {};
}

enum class Code;

int main() {
    create<Code>();
}

The linker complains about an unresolved symbol:

enum Code __cdecl create<enum Code>(void)

  • The linker issue disappears if you leave out the return value (replace T with void).
  • The problem remains when explicitly specifying the underlying type.
  • The problem remains if we replace the scoped enumeration with an unscoped enumeration. We do not have to specify the underlying type when using Microsoft's C++ extensions (/Ze). Nothing changes when specifying the underlying type.
dyp
  • 38,334
  • 13
  • 112
  • 177
-2

I think you should declare None in header (.h) and define it in source file(.cpp)

Foo.h

namespace Error {
    extern const Code None;
}

Foo.cpp

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

Sometimes enum will be optimized and will have no instance or address, especially you declare it as a static variable.

owent
  • 118
  • 4