I was writing a compile-time parser but I am stuck on a problem which I don't know how to solve in C++. I am using Microsoft Visual Studio Community 2019, version 17.8.3 (latest).
This is the code which is giving me a compiler error:
#include <array>
#include <variant>
using namespace std;
enum class TokenErrors
{
UNINITIALISED,
TK_ERROR_SYMBOL,
TK_ERROR_PATTERN,
TK_EOF
};
enum class MyTokenType
{
TK_SYMBOL
};
template <typename TokenType, int num_states>
struct Lexer
{
std::array<TokenType, num_states> final_states;
};
static consteval auto foo()
{
auto lxr = Lexer<std::variant<MyTokenType, TokenErrors>, 5>{};
for (auto &x: lxr.final_states)
x = TokenErrors::UNINITIALISED;
return lxr;
}
int main()
{
auto res = foo();
}
I tried running this code. Visual Studio is giving me this error while compiling:
Build started at 17:22...
1>------ Build started: Project: EfficientCompiler, Configuration: Debug x64 ------
1>Scanning sources for module dependencies...
1>main.cpp
1>C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19): error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'Lexer<std::variant<MyTokenType,TokenErrors>,5>'
1>C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1>Invalid aggregate initialization
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> 'initializing': cannot convert from 'initializer list' to 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> Too many braces around initializer for 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> 'initializing': cannot convert from 'initializer list' to 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> Too many braces around initializer for 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> 'initializing': cannot convert from 'initializer list' to 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> Too many braces around initializer for 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> 'initializing': cannot convert from 'initializer list' to 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> Too many braces around initializer for 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> 'initializing': cannot convert from 'initializer list' to 'MyTokenType'
1> C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,19):
1> Too many braces around initializer for 'MyTokenType'
1>C:\Users\Aakash\source\repos\EfficientCompiler\EfficientCompiler\main.cpp(35,16): error C7595: 'foo': call to immediate function is not a constant expression
1>Done building project "EfficientCompiler.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 17:22 and took 01.116 seconds ==========
When I convert the signature of function foo()
to static constexpr auto foo()
, the code compiles fine.
I am stuck on this problem, because as far as I know, std::variant
is constexpr
compatible (and it is compiling fine in constexpr
context too) and it should not result in this error in consteval
. Can someone help me here?
As mentioned in the comments, your code looks fine and this appears to be a bug in the MSVC compiler (which still persists in the version that ships with the 'latest' Visual Studio 2022). However, this question does not have the language-lawyer tag and I'm not going to try to give an authoritative explanation for why I consider it a bug (other than the fact that your code compiles on every other compiler I have tried).
The strictly correct way to fix this is to file a bug report with the Visual Studio team and wait for them to fix the problem. But that could be a (very) long wait.
In the meantime, I will suggest a possible workaround …
Some investigations revealed that, in your code, what appears to be upsetting/confusing the MSVC compiler is the attempt to initialize the elements of the
final_states
array with values that are not the first variant type. Thus, if you change thex = TokenErrors::UNINITIALISED;
line in thefor
loop tox = MyTokenType::TK_SYMBOL;
, your code then compiles. However, this makes a significant – and very likely undesirable – change to the code base.An alternative is to redefine the order of the
std::variant
types, so thatTokenErrors
becomes the first (i.e. default) object type. (This may also be undesirable, but it has less observable effects that the previous suggestion.) The following version of yourfoo
function compiles with the MSVC compiler in the VS-2019 (142) Toolset (with all other code being left unchanged from that you have posted):This may also not be an 'acceptable' change. If so, please let me know and I will attempt to find alternative workarounds. I have already tried many other tricks, including creating a local,
const
object of the appropriatestd::variant
type and using that to initialize thex
in the loop, but all these attempts have (so far) been unsuccessful.Here is one of those attempts, using the
explicit
constructor ofstd::variant
that takes astd::in_place_index<n>
as its first argument; with this, the code as presented compiles but, if the first line is replaced with the (commented-out) second, it fails with the error message you have cited:Note: In the above code (the last snippet), and elsewhere, replacing the loop with
lxr.final_states.fill(var);
also makes no difference.