ICE with MSVC 17.11.4/Boost Spirit on invalid code - anyone able to reduce further?
using Boost 1.86 and MSVC 17.11.4/x64 Debug
there is a real bug/problem ((non class)enum and struct using same name) and the code does not compile with recent clang-cl/clang/gcc: https://gcc.godbolt.org/z/YxfTn7PY6
but Microsoft CL gets an internal compiler error (ICE) crash on this when the static qi::rule is active - can't see the relation - so the crash showing "other" problems inside the compiler :)
even when the example is broken the compiler should not crash - two runtime debugger dialogs pops up on compile
i want to reduce it further before filing an issue - can someone see the relation to the qt::rule?
main.cpp
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
namespace qi = boost::spirit::qi;
enum Type{SameName};
struct SameName{};
BOOSTFUSIONADAPTSTRUCT( SameName )
#if 1
static qi::rule<std::stringview::constiterator, std::string()> somerule;
#endif
int main()
{
return 0;
}
CMakeLists.txt
cmakeminimumrequired(VERSION 3.28)
set (CMAKECXXSTANDARD 17)
project(ice)
addexecutable(icetests main.cpp)
# headers only - simplified (no package finding etc. needed for this ICE)
targetincludedirectories(icetests PUBLIC "../boost1860") # the zip-folder
how to install boost 1.86 (headers-only is enough - some seconds to build)
1. download:
https://archives.boost.io/release/1.86.0/source/
https://archives.boost.io/release/1.86.0/source/boost1860.7z
2. extract with 7zip
3. cd boost1860
4. bootstrap.bat
5. b2 headers
https://redd.it/1fythpe
@r_cpp
using Boost 1.86 and MSVC 17.11.4/x64 Debug
there is a real bug/problem ((non class)enum and struct using same name) and the code does not compile with recent clang-cl/clang/gcc: https://gcc.godbolt.org/z/YxfTn7PY6
but Microsoft CL gets an internal compiler error (ICE) crash on this when the static qi::rule is active - can't see the relation - so the crash showing "other" problems inside the compiler :)
even when the example is broken the compiler should not crash - two runtime debugger dialogs pops up on compile
i want to reduce it further before filing an issue - can someone see the relation to the qt::rule?
main.cpp
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
namespace qi = boost::spirit::qi;
enum Type{SameName};
struct SameName{};
BOOSTFUSIONADAPTSTRUCT( SameName )
#if 1
static qi::rule<std::stringview::constiterator, std::string()> somerule;
#endif
int main()
{
return 0;
}
CMakeLists.txt
cmakeminimumrequired(VERSION 3.28)
set (CMAKECXXSTANDARD 17)
project(ice)
addexecutable(icetests main.cpp)
# headers only - simplified (no package finding etc. needed for this ICE)
targetincludedirectories(icetests PUBLIC "../boost1860") # the zip-folder
how to install boost 1.86 (headers-only is enough - some seconds to build)
1. download:
https://archives.boost.io/release/1.86.0/source/
https://archives.boost.io/release/1.86.0/source/boost1860.7z
2. extract with 7zip
3. cd boost1860
4. bootstrap.bat
5. b2 headers
https://redd.it/1fythpe
@r_cpp
gcc.godbolt.org
Compiler Explorer - C++ (x86-64 gcc (trunk))
namespace qi = boost::spirit::qi;
enum Type
{
SameName
};
struct SameName
{
};
BOOST_FUSION_ADAPT_STRUCT( SameName )
static qi::rule<std::string_view::const_iterator, std::string()> some_rule;
int main()
{
return 0;
}
enum Type
{
SameName
};
struct SameName
{
};
BOOST_FUSION_ADAPT_STRUCT( SameName )
static qi::rule<std::string_view::const_iterator, std::string()> some_rule;
int main()
{
return 0;
}
error: no declaration matches 'int myclass::setnum(int)'
I genuinely don't understand where i've gone wrong with this incredibly basic experiment with classes. It returns an error for my set function but not my get function.
main.cpp
\#include <iostream>
\#include "myclass.h"
using namespace std;
int main()
{
myclass newobj;
cout << newobj.get\num();
return 0;
}
myclass.h
\#ifndef MYCLASS_H
\#define MYCLASS_H
class myclass
{
private:
int num = 10;
public:
myclass();
int get_num();
void set_num(int x);
};
\#endif // MYCLASS_H
myclass.cpp
\#include "myclass.h"
\#include <iostream>
using namespace std;
myclass::myclass()
{
cout << "poo";
}
myclass::get_num()
{
return num;
}
myclass::set_num(int x)
{
num = x;
}
The error is on line 16 of myclass.cpp
https://redd.it/1gf7v11
@r_cpp
I genuinely don't understand where i've gone wrong with this incredibly basic experiment with classes. It returns an error for my set function but not my get function.
main.cpp
\#include <iostream>
\#include "myclass.h"
using namespace std;
int main()
{
myclass newobj;
cout << newobj.get\num();
return 0;
}
myclass.h
\#ifndef MYCLASS_H
\#define MYCLASS_H
class myclass
{
private:
int num = 10;
public:
myclass();
int get_num();
void set_num(int x);
};
\#endif // MYCLASS_H
myclass.cpp
\#include "myclass.h"
\#include <iostream>
using namespace std;
myclass::myclass()
{
cout << "poo";
}
myclass::get_num()
{
return num;
}
myclass::set_num(int x)
{
num = x;
}
The error is on line 16 of myclass.cpp
https://redd.it/1gf7v11
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
How is min and max not defined here?
\#ifndef min
\#define min( a, b ) ( (a) <= (b) ? (a) : (b) )
\#endif
\#ifndef max
\#define max( a, b ) ( (a) >= (b) ? (a) : (b) )
\#endif
\#ifndef clamp
\#define clamp( x, a, b ) min( max( (x), (a) ), (b) )
\#endif
https://redd.it/1h3lhw5
@r_cpp
\#ifndef min
\#define min( a, b ) ( (a) <= (b) ? (a) : (b) )
\#endif
\#ifndef max
\#define max( a, b ) ( (a) >= (b) ? (a) : (b) )
\#endif
\#ifndef clamp
\#define clamp( x, a, b ) min( max( (x), (a) ), (b) )
\#endif
https://redd.it/1h3lhw5
@r_cpp
Reddit
How is min and max not defined here? : r/cpp
306K subscribers in the cpp community. Discussions, articles and news about the C++ programming language or programming in C++.
Coroutines promise type
Hi there,
I've recently learnt about coroutines, and got excited as I maintain several async libraries, for which to support coroutines for at least the base library -the task scheduler-.
The task scheduler serves bare functions: No parameters , no return value, and I want to keep it as it is while supporting coroutine to coawait for periods, primarily.
Therefore I went to have the promisetype::getreturnobject() to return void, as no intention to enforce application/user to switch such tasks to different function signature, and to avoid double managing the tasks.
I've initially implemented that, with a compile error preventing me to proceed:
Following is the main implementation of the coroutine, and here's the execution link: https://godbolt.org/z/4hWce9n6P
Am I getting coroutines wrong? What is suggested to do?
Thanks.
https://redd.it/1h4akh4
@r_cpp
Hi there,
I've recently learnt about coroutines, and got excited as I maintain several async libraries, for which to support coroutines for at least the base library -the task scheduler-.
The task scheduler serves bare functions: No parameters , no return value, and I want to keep it as it is while supporting coroutine to coawait for periods, primarily.
Therefore I went to have the promisetype::getreturnobject() to return void, as no intention to enforce application/user to switch such tasks to different function signature, and to avoid double managing the tasks.
I've initially implemented that, with a compile error preventing me to proceed:
error: unable to find the promise type for this coroutine.Following is the main implementation of the coroutine, and here's the execution link: https://godbolt.org/z/4hWce9n6P
Am I getting coroutines wrong? What is suggested to do?
Thanks.
class H4Delay {
uint32_t duration;
task* owner;
task* resumer=nullptr;
public:
class promise_type {
task* owner=nullptr;
friend class H4Delay;
public:
void get_return_object() noexcept {}
std::suspend_never initial_suspend() noexcept { return {}; }
void return_void() noexcept {}
void unhandled_exception() noexcept { std::terminate(); }
struct final_awaiter {
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<promise_type> h) noexcept {
auto owner = h.promise().owner;
if (owner) owner->_destruct();
task::suspendedTasks.erase(owner);
// [ ] IF NOT IMMEDIATEREQUEUE: MANAGE REQUEUE AND CHAIN CALLS.
}
void await_resume() noexcept {}
};
final_awaiter final_suspend() noexcept { return {}; }
};
std::coroutine_handle<promise_type> _coro;
H4Delay(uint32_t duration, task* caller=H4::context) : duration(duration), owner(caller) {}
~H4Delay() {
if (_coro) _coro.destroy();
}
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<promise_type> h) noexcept {
// Schedule the resumer.
resumer = h4.once(duration, [h]{ h.resume(); });
_coro = h;
_coro.promise().owner = owner;
task::suspendedTasks[owner] = this;
}
void await_resume() noexcept { resumer = nullptr; }
void cancel() { ... }
};
https://redd.it/1h4akh4
@r_cpp
godbolt.org
Compiler Explorer - C++ (x86-64 gcc 13.2)
///////////////////////////////////////////////////////////////////////////
// Example source code for blog post:
// "C++ Coroutines: Understanding Symmetric-Transfer"
//
// Implementation of a naive 'task' coroutine type.
// using namespace std;
#ifndef…
// Example source code for blog post:
// "C++ Coroutines: Understanding Symmetric-Transfer"
//
// Implementation of a naive 'task' coroutine type.
// using namespace std;
#ifndef…
ImGui::NewFrame() throwing an error after second call
The error is: "abort() has been called"
This is the code:
`//io.ConfigFlags`
https://redd.it/1harvjb
@r_cpp
The error is: "abort() has been called"
This is the code:
#include <enet/enet.h>#include <glad/glad.h>#include <GLFW/glfw3.h>#include <stb_image/stb_image.h>#include <stb_truetype/stb_truetype.h>#include "gl2d/gl2d.h"#include <iostream>#include <ctime>#include "platformTools.h"#include <raudio.h>#include "platformInput.h"#include "otherPlatformFunctions.h"#include "gameLayer.h"#include <fstream>#include <chrono>#include "errorReporting.h"#include "imgui.h"#include "backends/imgui_impl_glfw.h"#include "backends/imgui_impl_opengl3.h"#include "imguiThemes.h"#ifdef _WIN32#include <Windows.h>#endif#undef min#undef maxint main(){GLFWwindow* window;#pragma region window and openglpermaAssertComment(glfwInit(), "err initializing glfw");glfwWindowHint(GLFW_SAMPLES, 4);#ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);#endifint w = 500;int h = 500;window = glfwCreateWindow(w, h, "Window", nullptr, nullptr);glfwMakeContextCurrent(window);glfwSwapInterval(1);//permaAssertComment(gladLoadGL(), "err initializing glad");permaAssertComment(gladLoadGLLoader((GLADloadproc)glfwGetProcAddress), "err initializing glad");#pragma endregion#pragma region gl2dgl2d::init();#pragma endregion#pragma region imguiImGui::CreateContext();imguiThemes::embraceTheDarkness();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;`//io.ConfigFlags`
|= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform WindowsImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.Colors[ImGuiCol_WindowBg].w = 0.f;style.Colors[ImGuiCol_DockingEmptyBg].w = 0.f;}ImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 330");#pragma endregionwhile (!glfwWindowShouldClose(window)){glfwPollEvents();ImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);ImGui::NewFrame();ImGui::Begin("My Scene");ImGui::End();ImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());glfwSwapBuffers(window);}}https://redd.it/1harvjb
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
I don't understand how compilers handle lambda expressions in unevaluated contexts
Lambda expressions are more powerful than just being syntactic sugar for structs with operator(). You can use them in places that otherwise do not allow the declaration or definition of a new class.
For example:
template<typename T, typename F = decltype(
(auto a, auto b){ return a < b;} )>
auto compare(T a, T b, F comp = F{}) {
return comp(a,b);
}
is an absolutely terrible function, probably sabotage. Why?
Every template instantiation creates a different lamba, therefore a different type and a different function signature. This makes the lambda expression very different from the otherwise similar std::less.
I use static_assert to check this for templated types:
template<typename T, typename F = decltype((){} )>
struct Type {T value;};
template<typename T>
Type(T) -> Type<T>;
staticassert(not std::issamev<Type<int>,Type<int>>);
Now, why are these types the same, when I use the deduction guide?
staticassert(std::issamev<decltype(Type(1)),decltype(Type(1))>);
All three major compilers agree here and disagree with my intuition that the types should be just as different as in the first example.
I also found a way for clang to give a different result when I add template aliases to the mix:
template<typename T>
using C = Type<T>;
#if defined(clang)
staticassert(not std::issamev<C<int>,C<int>>);
#else
staticassert(std::issamev<C<int>,C<int>>);
#endif
So I'm pretty sure at least one compiler is wrong at least once, but I would like to know, whether they should all agree all the time that the types are different.
Compiler Explorer: https://godbolt.org/z/1fTa1vsTK
https://redd.it/1i3rpsm
@r_cpp
Lambda expressions are more powerful than just being syntactic sugar for structs with operator(). You can use them in places that otherwise do not allow the declaration or definition of a new class.
For example:
template<typename T, typename F = decltype(
(auto a, auto b){ return a < b;} )>
auto compare(T a, T b, F comp = F{}) {
return comp(a,b);
}
is an absolutely terrible function, probably sabotage. Why?
Every template instantiation creates a different lamba, therefore a different type and a different function signature. This makes the lambda expression very different from the otherwise similar std::less.
I use static_assert to check this for templated types:
template<typename T, typename F = decltype((){} )>
struct Type {T value;};
template<typename T>
Type(T) -> Type<T>;
staticassert(not std::issamev<Type<int>,Type<int>>);
Now, why are these types the same, when I use the deduction guide?
staticassert(std::issamev<decltype(Type(1)),decltype(Type(1))>);
All three major compilers agree here and disagree with my intuition that the types should be just as different as in the first example.
I also found a way for clang to give a different result when I add template aliases to the mix:
template<typename T>
using C = Type<T>;
#if defined(clang)
staticassert(not std::issamev<C<int>,C<int>>);
#else
staticassert(std::issamev<C<int>,C<int>>);
#endif
So I'm pretty sure at least one compiler is wrong at least once, but I would like to know, whether they should all agree all the time that the types are different.
Compiler Explorer: https://godbolt.org/z/1fTa1vsTK
https://redd.it/1i3rpsm
@r_cpp
godbolt.org
Compiler Explorer - C++
template<typename T, typename F = decltype([](){} )>
struct Type {T value;};
template<typename T>
Type(T) -> Type<T>;
static_assert(not std::is_same_v<Type<int>,Type<int>>);
static_assert(std::is_same_v<decltype(Type(1)),decltype(Type(1))>);
template<typename…
struct Type {T value;};
template<typename T>
Type(T) -> Type<T>;
static_assert(not std::is_same_v<Type<int>,Type<int>>);
static_assert(std::is_same_v<decltype(Type(1)),decltype(Type(1))>);
template<typename…
Can I put import inside the global module fragment?
So I am working on importizer that automatically create a module from a header file. It does so by collecting preprocessor directives, especially conditional ones, that has a #include inside and recreate it on top. For example:
// File.h
#pragma once
#ifdef COND
#include <vector>
#include <modularizedHeader.h>
#endif
will create this preamble
module;
#ifdef COND
#include <vector>
#endif
export module File;
#ifdef COND
import modularizedHeader;
#endif
which repeats the condition twice. With more complex conditions, the length will very quickly get out of hand.
Can I put import in the GMF like this to save some space?
module;
#ifdef COND
#include <vector>
import modularizedHeader;
#endif
export module File;
I was suspicious at first so I tested this approach on Godbolt (try putting the import into the GMF), and it's fine. I even read the C++ standard for modules, and I don't see any regulation about their location. Moreover, on cppreference, only preprocessing directives can go into the GMF, and import does count as one.
Is there any problem with doing it like this, and is there a better way to repeat the condition only once?
https://redd.it/1ihbb9f
@r_cpp
So I am working on importizer that automatically create a module from a header file. It does so by collecting preprocessor directives, especially conditional ones, that has a #include inside and recreate it on top. For example:
// File.h
#pragma once
#ifdef COND
#include <vector>
#include <modularizedHeader.h>
#endif
will create this preamble
module;
#ifdef COND
#include <vector>
#endif
export module File;
#ifdef COND
import modularizedHeader;
#endif
which repeats the condition twice. With more complex conditions, the length will very quickly get out of hand.
Can I put import in the GMF like this to save some space?
module;
#ifdef COND
#include <vector>
import modularizedHeader;
#endif
export module File;
I was suspicious at first so I tested this approach on Godbolt (try putting the import into the GMF), and it's fine. I even read the C++ standard for modules, and I don't see any regulation about their location. Moreover, on cppreference, only preprocessing directives can go into the GMF, and import does count as one.
Is there any problem with doing it like this, and is there a better way to repeat the condition only once?
https://redd.it/1ihbb9f
@r_cpp
GitHub
GitHub - msqr1/importizer: Backward compatibly refactor header-based C++ into modules.
Backward compatibly refactor header-based C++ into modules. - msqr1/importizer
Zero-cost C++ wrapper pattern for a ref-counted C handle
Hello, fellow C++ enthusiasts!
I want to create a 0-cost C++ wrapper for a ref-counted C handle without UB, but it doesn't seem possible. Below is as far as I can get (thanks https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html) :
// ---------------- C library ----------------
#ifdef __cplusplus
extern "C" {
#endif
struct ctrl_block { /* ref-count stuff */ };
struct soo {
char storageForCppWrapper; // Here what I paid at runtime (one byte + alignement) (let's label it #1)
/* real data lives here */
};
void useSoo(soo*);
void useConstSoo(const soo*);
struct shared_soo {
soo* data;
ctrl_block* block;
};
// returns {data, ref-count}
// data is allocated with malloc which create ton of implicit-lifetime type
shared_soo createSoo();
#ifdef __cplusplus
}
#endif
// -------------- C++ wrapper --------------
template<class T>
class SharedPtr {
public:
SharedPtr(T* d, ctrl_block* b) : data{ d }, block{ b } {}
T* operator->() { return data; }
// ref-count methods elided
private:
T* data;
ctrl_block* block;
};
// The size of alignement of Coo is 1, so it can be stored in storageForCppWrapper
class Coo {
public:
// This is the second issue, it exists and is public so that Coo has a trivial lifetime, but it shall never actually be used... (let's label it #2)
Coo() = default;
Coo(Coo&&) = delete;
Coo(const Coo&) = delete;
Coo& operator=(Coo&&) = delete;
Coo& operator=(const Coo&) = delete;
void use() { useSoo(get()); }
void use() const { useConstSoo(get()); }
static SharedPtr<Coo> create()
{
auto s = createSoo();
return { reinterpret_cast<Coo*>(s.data), s.block };
}
private:
soo* get() { return reinterpret_cast<soo*>(this); }
const soo* get() const { return reinterpret_cast<const soo*>(this); }
};
int main() {
auto coo = Coo::create();
coo->use(); // The syntaxic sugar I want for the user of my lib (let's label it #3)
return 0;
}
**Why not use the classic Pimpl?**
Because the ref-counting pushes the real data onto the heap while the Pimpl shell stays on the stack. A `SharedPtr<PimplSoo>` would then break the `SharedPtr` contract: should `get()` return the C++ wrapper (whose lifetime is now independent of the smart-pointer) or the raw C `soo` handle (which no longer matches the template parameter)? Either choice is wrong, so Pimpl just doesn’t fit here.
**Why not rely on “link-time aliasing”?**
The idea is to wrap the header in
# ifdef __cplusplus
\* C++ view of the type *\
# else
\* C view of the type *\
# endif
so the same symbol has two different definitions, one for C and one for C++. While this *usually* works, the Standard gives it no formal blessing (probably because it is ABI related). It blows past the One Definition Rule, disables meaningful type-checking, and rests entirely on unspecified layout-compatibility. In other words, it’s a stealth `cast` that works but carries no guarantees.
**Why not use** `std::start_lifetime_as` **?**
The call itself doesn’t read or write memory, but the Standard says that *starting* an object’s lifetime concurrently is undefined behaviour. In other words, it isn’t “zero-cost”: you must either guarantee single-threaded use or add synchronisation around the call. That extra coordination defeats the whole point of a free-standing, zero-overhead wrapper (unless I’ve missed something).
**Why this approach (I did not find an existing name for it so lets call it "reinterpret this")**
I am not sure, but this code seems fine from a standard point of view (even "#3"), isn't it ? Afaik, #3
Hello, fellow C++ enthusiasts!
I want to create a 0-cost C++ wrapper for a ref-counted C handle without UB, but it doesn't seem possible. Below is as far as I can get (thanks https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html) :
// ---------------- C library ----------------
#ifdef __cplusplus
extern "C" {
#endif
struct ctrl_block { /* ref-count stuff */ };
struct soo {
char storageForCppWrapper; // Here what I paid at runtime (one byte + alignement) (let's label it #1)
/* real data lives here */
};
void useSoo(soo*);
void useConstSoo(const soo*);
struct shared_soo {
soo* data;
ctrl_block* block;
};
// returns {data, ref-count}
// data is allocated with malloc which create ton of implicit-lifetime type
shared_soo createSoo();
#ifdef __cplusplus
}
#endif
// -------------- C++ wrapper --------------
template<class T>
class SharedPtr {
public:
SharedPtr(T* d, ctrl_block* b) : data{ d }, block{ b } {}
T* operator->() { return data; }
// ref-count methods elided
private:
T* data;
ctrl_block* block;
};
// The size of alignement of Coo is 1, so it can be stored in storageForCppWrapper
class Coo {
public:
// This is the second issue, it exists and is public so that Coo has a trivial lifetime, but it shall never actually be used... (let's label it #2)
Coo() = default;
Coo(Coo&&) = delete;
Coo(const Coo&) = delete;
Coo& operator=(Coo&&) = delete;
Coo& operator=(const Coo&) = delete;
void use() { useSoo(get()); }
void use() const { useConstSoo(get()); }
static SharedPtr<Coo> create()
{
auto s = createSoo();
return { reinterpret_cast<Coo*>(s.data), s.block };
}
private:
soo* get() { return reinterpret_cast<soo*>(this); }
const soo* get() const { return reinterpret_cast<const soo*>(this); }
};
int main() {
auto coo = Coo::create();
coo->use(); // The syntaxic sugar I want for the user of my lib (let's label it #3)
return 0;
}
**Why not use the classic Pimpl?**
Because the ref-counting pushes the real data onto the heap while the Pimpl shell stays on the stack. A `SharedPtr<PimplSoo>` would then break the `SharedPtr` contract: should `get()` return the C++ wrapper (whose lifetime is now independent of the smart-pointer) or the raw C `soo` handle (which no longer matches the template parameter)? Either choice is wrong, so Pimpl just doesn’t fit here.
**Why not rely on “link-time aliasing”?**
The idea is to wrap the header in
# ifdef __cplusplus
\* C++ view of the type *\
# else
\* C view of the type *\
# endif
so the same symbol has two different definitions, one for C and one for C++. While this *usually* works, the Standard gives it no formal blessing (probably because it is ABI related). It blows past the One Definition Rule, disables meaningful type-checking, and rests entirely on unspecified layout-compatibility. In other words, it’s a stealth `cast` that works but carries no guarantees.
**Why not use** `std::start_lifetime_as` **?**
The call itself doesn’t read or write memory, but the Standard says that *starting* an object’s lifetime concurrently is undefined behaviour. In other words, it isn’t “zero-cost”: you must either guarantee single-threaded use or add synchronisation around the call. That extra coordination defeats the whole point of a free-standing, zero-overhead wrapper (unless I’ve missed something).
**Why this approach (I did not find an existing name for it so lets call it "reinterpret this")**
I am not sure, but this code seems fine from a standard point of view (even "#3"), isn't it ? Afaik, #3
+2913,7 @@ static void* vma_aligned_alloc(size_t alignment, size_t size)
#include <AvailabilityMacros.h>
#endif
-static void* vma_aligned_alloc(size_t alignment, size_t size)
+inline void* vma_aligned_alloc(size_t alignment, size_t size)
{
// Unfortunately, aligned_alloc causes VMA to crash due to it returning null pointers. (At least under 11.4)
Also, GCC 15 still doesn't support private module fragment, so I enclosed `module :private;` line with `#ifndef __GNUC__ ... #endif`.
And I retried...
FAILED: CMakeFiles/vku.dir/interface/mod.cppm.o CMakeFiles/vku.dir/vku.gcm
/home/gk/gcc-15/bin/g++ -isystem /home/gk/Downloads/vku/build/vcpkg_installed/arm64-linux/include -isystem /home/gk/Downloads/vku/build/vcpkg_installed/arm64-linux/share/unofficial-vulkan-memory-allocator-hpp/../../include -std=gnu++23 -MD -MT CMakeFiles/vku.dir/interface/mod.cppm.o -MF CMakeFiles/vku.dir/interface/mod.cppm.o.d -fmodules-ts -fmodule-mapper=CMakeFiles/vku.dir/interface/mod.cppm.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o CMakeFiles/vku.dir/interface/mod.cppm.o -c /home/gk/Downloads/vku/interface/mod.cppm
In module imported at /home/gk/Downloads/vku/interface/debugging.cppm:11:1,
of module vku:debugging, imported at /home/gk/Downloads/vku/interface/mod.cppm:7:
vku:details.to_string: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/descriptors/PoolSizes.cppm:12:1,
of module vku:descriptors.PoolSizes, imported at /home/gk/Downloads/vku/interface/descriptors/mod.cppm:9,
of module vku:descriptors, imported at /home/gk/Downloads/vku/interface/mod.cppm:8:
vku:details.concepts: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/descriptors/DescriptorSetLayout.cppm:16:1,
of module vku:descriptors.DescriptorSetLayout, imported at /home/gk/Downloads/vku/interface/descriptors/mod.cppm:7,
of module vku:descriptors, imported at /home/gk/Downloads/vku/interface/mod.cppm:8:
vku:details.functional: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/commands.cppm:14:1,
of module vku:commands, imported at /home/gk/Downloads/vku/interface/mod.cppm:10:
vku:details.container.OnDemandCounterStorage: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/commands.cppm:15:1,
of module vku:commands, imported at /home/gk/Downloads/vku/interface/mod.cppm:10:
vku:details.tuple: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/rendering/AttachmentGroup.cppm:15:1,
of module vku:rendering.AttachmentGroup, imported at /home/gk/Downloads/vku/interface/rendering/mod.cppm:8,
of module vku:rendering, imported at /home/gk/Downloads/vku/interface/mod.cppm:14:
vku:rendering.AttachmentGroupBase: error: interface partition is not exported
/home/gk/Downloads/vku/interface/mod.cppm:4: confused by earlier errors, bailing out
ninja: build stopped: subcommand failed.
I wrote the `detail::` namespace code into the separate module partitions, and they were only imported to the module partitions and not exported. But GCC requires them to be exported if an exported module partition is importing them.
diff --git a/interface/mod.cppm b/interface/mod.cppm
index 1148398..695d987 100644
--- a/interface/mod.cppm
+++ b/interface/mod.cppm
@@ -13,3 +13,13 @@ export import :pipelines;
export import :queue;
export import :rendering;
export import :utils;
+
+#ifdef __GNUC__
+// GCC requires all interface partitions to be exported.
+export import :details.to_string;
+export import :details.concepts;
+export import :details.functional;
+export import :details.container.OnDemandCounterStorage;
+export import :details.tuple;
+export import :rendering.AttachmentGroupBase;
+#endif
So I exported them... and retried again...
gk@fedora:~/Downloads/vku$ cmake
#include <AvailabilityMacros.h>
#endif
-static void* vma_aligned_alloc(size_t alignment, size_t size)
+inline void* vma_aligned_alloc(size_t alignment, size_t size)
{
// Unfortunately, aligned_alloc causes VMA to crash due to it returning null pointers. (At least under 11.4)
Also, GCC 15 still doesn't support private module fragment, so I enclosed `module :private;` line with `#ifndef __GNUC__ ... #endif`.
And I retried...
FAILED: CMakeFiles/vku.dir/interface/mod.cppm.o CMakeFiles/vku.dir/vku.gcm
/home/gk/gcc-15/bin/g++ -isystem /home/gk/Downloads/vku/build/vcpkg_installed/arm64-linux/include -isystem /home/gk/Downloads/vku/build/vcpkg_installed/arm64-linux/share/unofficial-vulkan-memory-allocator-hpp/../../include -std=gnu++23 -MD -MT CMakeFiles/vku.dir/interface/mod.cppm.o -MF CMakeFiles/vku.dir/interface/mod.cppm.o.d -fmodules-ts -fmodule-mapper=CMakeFiles/vku.dir/interface/mod.cppm.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o CMakeFiles/vku.dir/interface/mod.cppm.o -c /home/gk/Downloads/vku/interface/mod.cppm
In module imported at /home/gk/Downloads/vku/interface/debugging.cppm:11:1,
of module vku:debugging, imported at /home/gk/Downloads/vku/interface/mod.cppm:7:
vku:details.to_string: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/descriptors/PoolSizes.cppm:12:1,
of module vku:descriptors.PoolSizes, imported at /home/gk/Downloads/vku/interface/descriptors/mod.cppm:9,
of module vku:descriptors, imported at /home/gk/Downloads/vku/interface/mod.cppm:8:
vku:details.concepts: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/descriptors/DescriptorSetLayout.cppm:16:1,
of module vku:descriptors.DescriptorSetLayout, imported at /home/gk/Downloads/vku/interface/descriptors/mod.cppm:7,
of module vku:descriptors, imported at /home/gk/Downloads/vku/interface/mod.cppm:8:
vku:details.functional: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/commands.cppm:14:1,
of module vku:commands, imported at /home/gk/Downloads/vku/interface/mod.cppm:10:
vku:details.container.OnDemandCounterStorage: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/commands.cppm:15:1,
of module vku:commands, imported at /home/gk/Downloads/vku/interface/mod.cppm:10:
vku:details.tuple: error: interface partition is not exported
In module imported at /home/gk/Downloads/vku/interface/rendering/AttachmentGroup.cppm:15:1,
of module vku:rendering.AttachmentGroup, imported at /home/gk/Downloads/vku/interface/rendering/mod.cppm:8,
of module vku:rendering, imported at /home/gk/Downloads/vku/interface/mod.cppm:14:
vku:rendering.AttachmentGroupBase: error: interface partition is not exported
/home/gk/Downloads/vku/interface/mod.cppm:4: confused by earlier errors, bailing out
ninja: build stopped: subcommand failed.
I wrote the `detail::` namespace code into the separate module partitions, and they were only imported to the module partitions and not exported. But GCC requires them to be exported if an exported module partition is importing them.
diff --git a/interface/mod.cppm b/interface/mod.cppm
index 1148398..695d987 100644
--- a/interface/mod.cppm
+++ b/interface/mod.cppm
@@ -13,3 +13,13 @@ export import :pipelines;
export import :queue;
export import :rendering;
export import :utils;
+
+#ifdef __GNUC__
+// GCC requires all interface partitions to be exported.
+export import :details.to_string;
+export import :details.concepts;
+export import :details.functional;
+export import :details.container.OnDemandCounterStorage;
+export import :details.tuple;
+export import :rendering.AttachmentGroupBase;
+#endif
So I exported them... and retried again...
gk@fedora:~/Downloads/vku$ cmake
Can I put module declarations in header files?
Issue: https://github.com/Cvelth/vkfw/issues/19
So a while ago, I added module support to the
// ...
#ifdef VKFWMODULEIMPLEMENTATION
export module vkfw;
#endif
// ...
so that the
module;
#define VKFWMODULEIMPLEMENTATION
#include <vkfw/vkfw.hpp>
However, GCC 15+ rejects compilation with
In file included from .../vkfw-src/include/vkfw/vkfw.cppm:3:
.../vkfw-src/include/vkfw/vkfw.hpp:219:8:
error: module control-line cannot be in included file
However, I can't find anywhere in the spec/cppreference that disallow this. So is this allowed at all, or it's just a GCC limitation?
https://redd.it/1lw2g0d
@r_cpp
Issue: https://github.com/Cvelth/vkfw/issues/19
So a while ago, I added module support to the
vkfw library. It works fine for my usage with Clang, but recently (not really, it's been a while) GCC 15 released with module support finally stabilized. However, the way that module support is implemented is that in the header file vkfw.hpp, there is something like:// ...
#ifdef VKFWMODULEIMPLEMENTATION
export module vkfw;
#endif
// ...
so that the
vkfw.cpp file can be just:module;
#define VKFWMODULEIMPLEMENTATION
#include <vkfw/vkfw.hpp>
However, GCC 15+ rejects compilation with
In file included from .../vkfw-src/include/vkfw/vkfw.cppm:3:
.../vkfw-src/include/vkfw/vkfw.hpp:219:8:
error: module control-line cannot be in included file
However, I can't find anywhere in the spec/cppreference that disallow this. So is this allowed at all, or it's just a GCC limitation?
https://redd.it/1lw2g0d
@r_cpp
GitHub
The current module implementation does not work with GCC · Issue #19 · Cvelth/vkfw
Attempting to import vkfw as a C++ module using GCC 16.0 yields the following error: In file included from .../vkfw-src/include/vkfw/vkfw.cppm:3: .../vkfw-src/include/vkfw/vkfw.hpp:219:8: error: mo...
T::ComponentType*, Error> temp = getComponentForEntity<typename T::ComponentType>(entity);
if (temp.has_value())
T{}(*temp.value());
}
template<std::derived_from<Component> T>
std::unordered_map<Entity, T>& ECS::getComponentMap()
{
static std::unordered_map<Entity, T> instance{};
return instance;
}
}
#endif
https://redd.it/1nyjayx
@r_cpp
if (temp.has_value())
T{}(*temp.value());
}
template<std::derived_from<Component> T>
std::unordered_map<Entity, T>& ECS::getComponentMap()
{
static std::unordered_map<Entity, T> instance{};
return instance;
}
}
#endif
https://redd.it/1nyjayx
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
state!");
}
} else {
destroy();
_tag = other._tag;
switch (_tag) {
case Tag::Ok: new (&_value.ok) T(other._value.ok); break;
case Tag::Err: new (&_value.error) E(other._value.error); break;
default:
panic("[_tag] was left in an invalid state!");
}
}
return *this;
}
Result &operator=(Result &&other) noexcept {
if (this == &other) {
return *this;
}
if (_tag == other._tag) {
switch (_tag) {
case Tag::Ok: _value.ok = move(other._value.ok); break;
case Tag::Err: _value.error = move(other._value.error); break;
default:
panic("[_tag] was left in an invalid state!");
}
} else {
destroy();
_tag = other._tag;
switch (_tag) {
case Tag::Ok: new (&_value.ok) T(move(other._value.ok)); break;
case Tag::Err: new (&_value.error) E(move(other._value.error)); break;
default:
panic("[_tag] was left in an invalid state!");
}
}
return *this;
}
private:
enum class Tag {
Ok,
Err
} _tag;
union Variant {
T ok;
E error;
constexpr Variant() noexcept {}
~Variant() noexcept {}
} _value;
explicit Result(Tag tag, const T &ok, OkOverload)
: _tag{ tag } {
new (&_value.ok) T(ok);
}
explicit Result(Tag tag, const E &error, ErrOverload)
: _tag{ tag } {
new (&_value.error) E(error);
}
explicit Result(Tag tag, T &&ok, OkOverload)
: _tag{ tag } {
new (&_value.ok) T(move(ok));
}
explicit Result(Tag tag, E &&error, ErrOverload)
: _tag{ tag } {
new (&_value.error) E(move(error));
}
void destroy() noexcept {
switch (_tag) {
case Tag::Ok: _value.ok.~T(); break;
case Tag::Err: _value.error.~E(); break;
default:
panic("[_tag] was left in an invalid state!");
}
}
};
struct Unit {};
template<typename T>
using Option = Result<T, Unit>;
template<typename E>
using Fallible = Result<Unit, E>;
#endif
```
https://redd.it/1okpb9k
@r_cpp
}
} else {
destroy();
_tag = other._tag;
switch (_tag) {
case Tag::Ok: new (&_value.ok) T(other._value.ok); break;
case Tag::Err: new (&_value.error) E(other._value.error); break;
default:
panic("[_tag] was left in an invalid state!");
}
}
return *this;
}
Result &operator=(Result &&other) noexcept {
if (this == &other) {
return *this;
}
if (_tag == other._tag) {
switch (_tag) {
case Tag::Ok: _value.ok = move(other._value.ok); break;
case Tag::Err: _value.error = move(other._value.error); break;
default:
panic("[_tag] was left in an invalid state!");
}
} else {
destroy();
_tag = other._tag;
switch (_tag) {
case Tag::Ok: new (&_value.ok) T(move(other._value.ok)); break;
case Tag::Err: new (&_value.error) E(move(other._value.error)); break;
default:
panic("[_tag] was left in an invalid state!");
}
}
return *this;
}
private:
enum class Tag {
Ok,
Err
} _tag;
union Variant {
T ok;
E error;
constexpr Variant() noexcept {}
~Variant() noexcept {}
} _value;
explicit Result(Tag tag, const T &ok, OkOverload)
: _tag{ tag } {
new (&_value.ok) T(ok);
}
explicit Result(Tag tag, const E &error, ErrOverload)
: _tag{ tag } {
new (&_value.error) E(error);
}
explicit Result(Tag tag, T &&ok, OkOverload)
: _tag{ tag } {
new (&_value.ok) T(move(ok));
}
explicit Result(Tag tag, E &&error, ErrOverload)
: _tag{ tag } {
new (&_value.error) E(move(error));
}
void destroy() noexcept {
switch (_tag) {
case Tag::Ok: _value.ok.~T(); break;
case Tag::Err: _value.error.~E(); break;
default:
panic("[_tag] was left in an invalid state!");
}
}
};
struct Unit {};
template<typename T>
using Option = Result<T, Unit>;
template<typename E>
using Fallible = Result<Unit, E>;
#endif
```
https://redd.it/1okpb9k
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
26)
```cpp
constexpr auto parse() {
// Allowed
static_assert(false, "Expected ';'");
// Not allowed :( (until C++ 26)
std::size_t line = 5;
static_assert(false, "Expected ';' at line " + to_string(line));
}
```
I didn't want my project to require C++ 26, so I used another trick. The formatted string gets turned into a static array just like a vector (into `const_string` actually) and then it's passed into `ErrorMessage<const_string Msg>` that triggers compilation error. So we force the compiler to print the full type name that includes our error. But sadly the type name has a limit about 100 symbols. I think I could solve it with splitting the message into several ErrorMessages... God, I don't want to read this in my console.
```c++
template<const_string Msg>
struct ErrorMessage {
static_assert(false, "Check the template parameter for details");
};
template<auto err_getter>
consteval auto report_error() -> void {
// C++ 26 support
#ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT
static_assert(false, to_string(err_getter()));
#else
constexpr auto msg = const_string_from_string_view<[] { return to_string(err_getter()); }>();
std::ignore = ErrorMessage<msg>{};
#endif
}
```
I don't want you to see it, so I'll just show C++ 26 version.
```
error: static assertion failed: Lexer Error: Unterminated string at line 12
```
### Mapping signatures to names. And vice versa
In our little runtime C++ we are used to `std::unordered_map<string, value_t>` and other standard or non-standard (hello, Boost!) containers. But I needed a table where a key is a string and the value is a TYPE. And in C++ I can't treat types as values, I can't just put them into a dict... :(
So, welcome another hack!
```c++
template<auto, class>
struct signature_mapper;
// function_info_getter takes an index to our mapped function,
// and Is... holds all indices
template<auto function_info_getter, std::size_t... Is>
struct signature_mapper<
function_info_getter,
std::index_sequence<Is...>
> {
// hash func
consteval static auto hash(auto &&v) -> std::size_t {
return frozen::elsa<std::string_view>{}(v, 0);
}
// Our function overloaded with many unique types based on hash of the mapped function
constexpr static auto _overloaded = overloaded{
(
[](unique_type<hash(function_info_getter(Is).name)>)
-> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * {
return nullptr;
}
)...
};
// Extracting the type by name
template<const_string name>
using get_signature_t = std::remove_pointer_t<decltype(
_overloaded(
unique_type<hash(name)>{}
)
)>;
};
```
We use well-known function overload (~~but for evil things~~). Basically, one type inherits a lot of lambdas that take an empty `unique_type<hash>` that serves as our key and returns the pointer to our type.
```cpp
// How our mapper looks after expanding our params
struct overloaded : lambda1, lambda2, lambda3 {
using lambda1::operator();
using lambda2::operator();
using lambda3::operator();
};
// And every lambda looks like this
auto lambda_fib = [](unique_type<hash("fib")>) -> signature_of_fib* { return nullptr; };
```
When we call `_overloaded(unique_type<hash(name)>())` our poor compiler has to resolve the overload. And he looks for right one through all `()` operators. And then we just take that it returns (our `T*`) and get the `T`.
I use this "mechanism" to extract script functions into the native C++.
```cpp
constexpr auto script_fib = compile_result.function<"fib">();
```
### Bindings from C++ to our script lang
This was the most exhausting part. Well, how "exhausting" exactly... I was thinking for a few evenings and then made it work one morning. The problem was with me. I wanted to make a pretty API that was impossible in the current standard (maybe it's possible in C++ 26, but I didn't check it).
I wanted it to look like this:
```cpp
auto func() -> void;
auto foo(int) -> int;
// Примерно так
constexpr auto bindings = korka::make_bindings<
"func", func,
"foo", foo
>();
// Или так
constexpr auto
```cpp
constexpr auto parse() {
// Allowed
static_assert(false, "Expected ';'");
// Not allowed :( (until C++ 26)
std::size_t line = 5;
static_assert(false, "Expected ';' at line " + to_string(line));
}
```
I didn't want my project to require C++ 26, so I used another trick. The formatted string gets turned into a static array just like a vector (into `const_string` actually) and then it's passed into `ErrorMessage<const_string Msg>` that triggers compilation error. So we force the compiler to print the full type name that includes our error. But sadly the type name has a limit about 100 symbols. I think I could solve it with splitting the message into several ErrorMessages... God, I don't want to read this in my console.
```c++
template<const_string Msg>
struct ErrorMessage {
static_assert(false, "Check the template parameter for details");
};
template<auto err_getter>
consteval auto report_error() -> void {
// C++ 26 support
#ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT
static_assert(false, to_string(err_getter()));
#else
constexpr auto msg = const_string_from_string_view<[] { return to_string(err_getter()); }>();
std::ignore = ErrorMessage<msg>{};
#endif
}
```
I don't want you to see it, so I'll just show C++ 26 version.
```
error: static assertion failed: Lexer Error: Unterminated string at line 12
```
### Mapping signatures to names. And vice versa
In our little runtime C++ we are used to `std::unordered_map<string, value_t>` and other standard or non-standard (hello, Boost!) containers. But I needed a table where a key is a string and the value is a TYPE. And in C++ I can't treat types as values, I can't just put them into a dict... :(
So, welcome another hack!
```c++
template<auto, class>
struct signature_mapper;
// function_info_getter takes an index to our mapped function,
// and Is... holds all indices
template<auto function_info_getter, std::size_t... Is>
struct signature_mapper<
function_info_getter,
std::index_sequence<Is...>
> {
// hash func
consteval static auto hash(auto &&v) -> std::size_t {
return frozen::elsa<std::string_view>{}(v, 0);
}
// Our function overloaded with many unique types based on hash of the mapped function
constexpr static auto _overloaded = overloaded{
(
[](unique_type<hash(function_info_getter(Is).name)>)
-> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * {
return nullptr;
}
)...
};
// Extracting the type by name
template<const_string name>
using get_signature_t = std::remove_pointer_t<decltype(
_overloaded(
unique_type<hash(name)>{}
)
)>;
};
```
We use well-known function overload (~~but for evil things~~). Basically, one type inherits a lot of lambdas that take an empty `unique_type<hash>` that serves as our key and returns the pointer to our type.
```cpp
// How our mapper looks after expanding our params
struct overloaded : lambda1, lambda2, lambda3 {
using lambda1::operator();
using lambda2::operator();
using lambda3::operator();
};
// And every lambda looks like this
auto lambda_fib = [](unique_type<hash("fib")>) -> signature_of_fib* { return nullptr; };
```
When we call `_overloaded(unique_type<hash(name)>())` our poor compiler has to resolve the overload. And he looks for right one through all `()` operators. And then we just take that it returns (our `T*`) and get the `T`.
I use this "mechanism" to extract script functions into the native C++.
```cpp
constexpr auto script_fib = compile_result.function<"fib">();
```
### Bindings from C++ to our script lang
This was the most exhausting part. Well, how "exhausting" exactly... I was thinking for a few evenings and then made it work one morning. The problem was with me. I wanted to make a pretty API that was impossible in the current standard (maybe it's possible in C++ 26, but I didn't check it).
I wanted it to look like this:
```cpp
auto func() -> void;
auto foo(int) -> int;
// Примерно так
constexpr auto bindings = korka::make_bindings<
"func", func,
"foo", foo
>();
// Или так
constexpr auto