Skip to content

gxf_isaac_gems: make MachineEpsilon specializations inline - #76

Open
wolfv wants to merge 1 commit into
NVIDIA-ISAAC-ROS:mainfrom
wolfv:fix/epsilon-odr-inline
Open

gxf_isaac_gems: make MachineEpsilon specializations inline#76
wolfv wants to merge 1 commit into
NVIDIA-ISAAC-ROS:mainfrom
wolfv:fix/epsilon-odr-inline

Conversation

@wolfv

@wolfv wolfv commented Jul 29, 2026

Copy link
Copy Markdown

epsilon.hpp declares a variable template and two explicit specializations:

template <typename K>
constexpr K MachineEpsilon = K(0);
template <>
constexpr float MachineEpsilon<float> = 5.9604644775390625e-8f;
template <>
constexpr double MachineEpsilon<double> = 1.11022302462515654e-16;

In C++17 constexpr implies inline for variables, but an explicit specialization of a variable template is not a template -- it is an ordinary variable declaration, and the implicit inline does not apply to it. Each translation unit including the header therefore emits its own definition with external linkage, and linking two such objects together fails:

ld: detection3_d_array_message.cpp.o:(.rodata+0x38): multiple definition of
    'nvidia::isaac::MachineEpsilon<double>';
    nitros_detection3_d_array.cpp.o:(.rodata+0x48): first defined here
ld: detection3_d_array_message.cpp.o:(.rodata+0x40): multiple definition of
    'nvidia::isaac::MachineEpsilon<float>'; ...
collect2: error: ld returned 1 exit status

Adding inline gives the specializations vague linkage so the definitions are merged, which is what the primary template already gets.

Hit while building isaac_ros_nitros_detection3_d_array_type from source with GCC 15 and binutils ld. It is latent rather than new: any two translation units in one target that both include epsilon.hpp will collide, so it depends on how the sources happen to be grouped.

epsilon.hpp declares a variable template and two explicit specializations:

    template <typename K>
    constexpr K MachineEpsilon = K(0);
    template <>
    constexpr float MachineEpsilon<float> = 5.9604644775390625e-8f;
    template <>
    constexpr double MachineEpsilon<double> = 1.11022302462515654e-16;

In C++17 `constexpr` implies `inline` for variables, but an explicit
specialization of a variable template is not a template -- it is an ordinary
variable declaration, and the implicit inline does not apply to it. Each
translation unit including the header therefore emits its own definition with
external linkage, and linking two such objects together fails:

    ld: detection3_d_array_message.cpp.o:(.rodata+0x38): multiple definition of
        'nvidia::isaac::MachineEpsilon<double>';
        nitros_detection3_d_array.cpp.o:(.rodata+0x48): first defined here
    ld: detection3_d_array_message.cpp.o:(.rodata+0x40): multiple definition of
        'nvidia::isaac::MachineEpsilon<float>'; ...
    collect2: error: ld returned 1 exit status

Adding `inline` gives the specializations vague linkage so the definitions are
merged, which is what the primary template already gets.

Hit while building isaac_ros_nitros_detection3_d_array_type from source with GCC
15 and binutils ld. It is latent rather than new: any two translation units in one
target that both include epsilon.hpp will collide, so it depends on how the
sources happen to be grouped.

Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant