I know that variables/objects have internal and external linkage, but I didn't know that "types" have internal and external linkage. When I compile my code with GCC I'm given the warning:
‘ShaderModuleObject’ has a field ‘GFXAPIShaderModuleHandle ShaderModuleObject::handle’ whose type has internal linkage [-Wsubobject-linkage]
Here is the code, and I pasted it into Godbolt, but I don't see the same warning, or even if Godbolt has warnings enabled:
#define GFX_API_NULL_HANDLE 0
template <typename T, void(*deleter)(T)>
struct MoveOnlyGFXAPIHandle
{
MoveOnlyGFXAPIHandle() : handle(GFX_API_NULL_HANDLE) {}
MoveOnlyGFXAPIHandle(T handle) : handle(handle) {}
MoveOnlyGFXAPIHandle(const MoveOnlyGFXAPIHandle&) = delete;
MoveOnlyGFXAPIHandle(MoveOnlyGFXAPIHandle&& other) : handle(other.handle) { other.handle = GFX_API_NULL_HANDLE; };
MoveOnlyGFXAPIHandle& operator=(const MoveOnlyGFXAPIHandle&) = delete;
MoveOnlyGFXAPIHandle& operator=(MoveOnlyGFXAPIHandle&& other)
{
this->reset();
handle = other.handle;
other.handle = GFX_API_NULL_HANDLE;
return *this;
}
void reset() {
if (handle != GFX_API_NULL_HANDLE)
{
deleter(this->handle);
handle = GFX_API_NULL_HANDLE;
}
}
void assign(T handle) { reset(); this->handle = handle; }
operator T() const { return handle; }
T get() const { return handle; }
~MoveOnlyGFXAPIHandle() {
reset(); }
private:
T handle;
};
using GFXAPIShaderModuleHandle = MoveOnlyGFXAPIHandle < int,
+[](int handle) {
//destroyShaderModule(handle);
} > ;
struct ShaderModuleObject {
GFXAPIShaderModuleHandle handle;
};
int main()
{
ShaderModuleObject module_object;
return 0;
}
What are types with internal and external linkage? And why does my type have internal linkage, and why is that a bad thing such that GCC is warning me about it?
Here is MCVE:
// header.hpp
// a pointer to function is used as non-type template parameter
template <void ( * x_dummy )(void)>
struct Foo
{
};
// non-capturing lambda converted to pointer to function with unary + operator
using Bar = Foo< +[](void) { return; } >;
struct Frob
{
Bar bar;
};
// main.cpp
#include "header.hpp"
int main()
{
return 0;
}
g++ -c --std=c++20 ./main.cpp
In file included from ./main.cpp:1:
./header.hpp:10:8: warning: ‘Frob’ has a field ‘Bar Frob::bar’ whose type has internal linkage [-Wsubobject-linkage]
10 | struct Frob
| ^~~~
¿What is going on here? Type aliased by Bar
has internal linkage because it may be referenced by name only from current translation unit. This happens because it uses lambda (which by the way is anonymous, can not be referenced by name at all and therefore has no linkage) and including header.h
in different translation unit will result in different type being produced and ODR violation for Frob
.
This example is rather harmless (no problem since there is only a single translation unit), but in more complicated scenarios this may lead to strange bugs and mesed-up error message. For example if Bar
is used as part of DLL interface mangled symbol name may turn out to be different, depending on translation unit where header is included, producing cryptic linker errors:
__attribute__((visibility("default"))) void
Drob(Bar *);
// imported symbol
Drob(Foo<&{__lambda__#123()}::_FUN>*)
// exported symbol
Drob(Foo<&{__lambda__#456()}::_FUN>*)
Another MCVE where this kind of problem results in HARD error instead of a warning:
// header.hpp
#pragma once
template <void ( * x_deleter )(void)>
struct Foo
{
};
using Bar = Foo< +[](void) { return; } >;
void Drob(Bar *);
// header.cpp
#include "header.hpp"
void Drob(Bar *)
{
return;
}
// main.cpp
#include "header.hpp"
int main()
{
Drob(nullptr);
return 0;
}
g++ --std=c++20 -Wall ./main.cpp ./header.cpp
In file included from ./main.cpp:1:
./header.hpp:10:6: warning: ‘void Drob(Bar*)’ used but never defined
10 | void Drob(Bar *);
| ^~~~
./header.cpp:3:6: warning: ‘void Drob(Bar*)’ defined but not used [-Wunused-function]
3 | void Drob(Bar *)
| ^~~~
/usr/bin/ld: /tmp/ccY7YKJR.o: in function `main':
main.cpp:(.text+0xe): undefined reference to `Drob(Foo<&{lambda()#1}::_FUN>*)'
collect2: error: ld returned 1 exit status