diff --git a/Coder.Test/Languages/CppGeneratedSourceCompilesTests.cs b/Coder.Test/Languages/CppGeneratedSourceCompilesTests.cs
index 3de0193..95daef9 100644
--- a/Coder.Test/Languages/CppGeneratedSourceCompilesTests.cs
+++ b/Coder.Test/Languages/CppGeneratedSourceCompilesTests.cs
@@ -88,6 +88,46 @@ public void AnArrayDeclarationCompilesWhereverAnUnboundedOneMayStand()
});
}
+ ///
+ /// Tests that a class's mutable static fields compile, with and without an initialiser.
+ ///
+ ///
+ /// ISO C++ forbids an in-class initialiser on a non-const static data member, and the generator
+ /// writes one on every field — the value it was given, or {} when it was given none. Only
+ /// inline static lets both stand inside the class.
+ ///
+ [TestMethod]
+ public void AMutableStaticFieldCompilesInsideAClass()
+ {
+ string? compiler = ToolchainHarness.FindOnPath("--version", Compilers);
+ if (compiler is null)
+ {
+ Assert.Inconclusive("No C++ compiler on the path, so nothing was compiled.");
+ return;
+ }
+
+ SourceFile file = new("statics") { IsHeader = true };
+ ClassDeclaration config = new("Cfg") { Kind = TypeDeclarationKind.Struct };
+ config.Members.Add(new FieldDeclaration("count", "int") { IsStatic = true, InitialValue = new LiteralExpression(0) });
+ config.Members.Add(new FieldDeclaration("limit", "int") { IsStatic = true });
+ file.Members.Add(config);
+
+ ToolchainHarness.InTemporaryDirectory(directory =>
+ {
+ File.WriteAllText(Path.Combine(directory, "statics.h"), new CppGenerator().Generate(file));
+ File.WriteAllText(
+ Path.Combine(directory, "driver.cpp"),
+ "#include \"statics.h\"\n\nint main()\n{\n\tCfg::count = Cfg::limit + 1;\n\treturn Cfg::count * 0;\n}\n");
+
+ (int exitCode, string output) = ToolchainHarness.Run(
+ compiler,
+ "-std=c++20 -Wall -Wextra -pedantic -c driver.cpp -o driver.o",
+ directory);
+
+ Assert.AreEqual(0, exitCode, $"{compiler} rejected the generated header:{Environment.NewLine}{output}");
+ });
+ }
+
///
/// Builds a header declaring an array in each position one with no bound may stand in.
///
diff --git a/Coder/Languages/CppGenerator.cs b/Coder/Languages/CppGenerator.cs
index 1fb8d7e..21b4d98 100644
--- a/Coder/Languages/CppGenerator.cs
+++ b/Coder/Languages/CppGenerator.cs
@@ -37,8 +37,8 @@ public class CppGenerator : CFamilyGenerator
///
///
/// A depth rather than a flag, so a type declared inside a type leaves the count right when it
- /// closes. The one thing it decides is whether a constant field says inline or
- /// static; see .
+ /// closes. What it decides is how a constant or static field spells its storage; see
+ /// .
///
private int insideType;
@@ -794,7 +794,14 @@ private string SpellStorage(FieldDeclaration field)
return insideType > 0 ? "static constexpr " : "inline constexpr ";
}
- return field.IsStatic ? "static " : string.Empty;
+ // A non-const static data member may only carry an in-class initialiser when it is inline,
+ // and every field is written with one, so inside a type the plain keyword never compiles.
+ if (field.IsStatic)
+ {
+ return insideType > 0 ? "inline static " : "static ";
+ }
+
+ return string.Empty;
}
///