4

I am currently trying to have a compile-time constant variable that is template specialized for several types. Currently, I am using constant expressions such as the following generalized example:

template<typename T>
constexpr T GENERAL_CONSTANT = T(0.01);

template<> constexpr float GENERAL_CONSTANT<float> = float(0.02);

template<> constexpr double GENERAL_CONSTANT<double> = double(0.03);

This code however only seems to work on some compilers/linkers. It will compile and work correctly for Clang 9.0.0 for Windows 10, Clang 6.0.0 for Windows 10, Clang 6.0.0 for Ubuntu 18.04, and GCC for Ubuntu 18.04. But has given the similar multiple redefinition errors in several other configurations such as Clang 10.0.0 on Windows 10 or Clang 10.0.0 on Unix, as well as a few others. The errors will often look similar to this:

/usr/bin/ld: <some path to a.cpp> multiple definition of `GENERAL_CONSTANT<double>'; <some path to a.cpp>: first defined here
/usr/bin/ld: <some path to a.cpp> multiple definition of `GENERAL_CONSTANT<float>'; <some path to a.cpp>: first defined here

Where 'a.cpp' is the file that uses the constant, but does not define them. So given that this error is happening inconsistently depending on compiler and machine, I was curious if this is a non-standard approach to this problem, and if that is true, what approach should I take instead?

  • You could wrap it a struct. – Taekahn Jun 01 '20 at 19:39
  • Also, it should be `template<> constexpr float GENERAL_CONSTANT = float(0.02);` – Piotr Skotnicki Jun 01 '20 at 19:41
  • @PiotrSkotnicki You are correct, I had forgot to add the template parameter above, but had it in my code. As for more details, the variable templates are defined in a header file, and the constants are only used in source files which implement classes/structures that are also templated by type, i.e. float and double usually. – Henry Haase Jun 01 '20 at 19:46

2 Answers2

4

const qualification of a variable template (and constexpr does makes an object const) does not imply internal linkage, [basic.link]/p3:

A name having namespace scope has internal linkage if it is the name of

  • a variable, variable template, function, or function template that is explicitly declared static; or

  • a non-template variable of non-volatile const-qualified type, unless [...]

It seems to be a recent change (CWG 2387):

Notes from the December, 2018 teleconference:

CWG felt that a const type should not affect the linkage of a variable template or its instances.

and that explains the difference that you observed in Clang-10.

As a solution, mark the variable template and its specializations either static or inline. The former forces internal linkage, while the latter excludes the variable template instances from the one definition rule allowing for multiple definitions provided they are in separate translation units.

Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Ok good to know, adding the 'inline' keyword does appear to have worked for the problematic configurations. I think from the synopsis you gave, using 'inline' will be the best options for my uses. Thanks! – Henry Haase Jun 01 '20 at 20:39
2

This code does compile as is. See This Demo. Specialized template variables will still compile into the binary, even when marked constexpr. Since you are using the header and thus these variables in multiple translation units, you are getting a linker error. You can tell this by the fact that ld is giving you the error.

Marking the specializations inline will prevent this from happening and fix the issue:

template<typename T>
constexpr T GENERAL_CONSTANT = T(0.01);

template<> inline constexpr float GENERAL_CONSTANT<float> = float(0.02);

template<> inline constexpr double GENERAL_CONSTANT<double> = double(0.03);

This is a compiler bug, because constexpr implies inline for variables and functions but apparently, certain compiler versions mess this up for template specializatons.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • Ah ok good to know, adding the inline keyword does appear to have fixed the issue on the problematic configurations. Thanks for the help! – Henry Haase Jun 01 '20 at 20:32
  • 1
    This is not a compiler bug. Neither `constexpr` implies `inline` for non-member variables. You probably meant *"internal linkage"*. – Piotr Skotnicki Jun 02 '20 at 06:53