2

Usually, we do declare but not define a global variable in the header file. However, we define templates in it. Then the issue arises: is it possible to define a global variable template?

template <uint8_t PinCode>
uint8_t BitMask = digitalPinToBitMask(PinCode);

In reality, a global variable template:

  • instantizes only if at least one compile unit (CU) requires it.
  • causes no redefinition errors if multiple CUs require the same instance.
  • is shared among CUs, i.e., if one CU changes the value of an instance, other CUs requiring the same instance will be affected.
  • calls the initializer only once for each kind of required instance.
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982

3 Answers3

2

After longer research, I finally found the most elegant solution. The definition style depends on whether you want to initialize the variable.

If the variable does not need initialization, you only need a header file:

template <int TimerCode>
int TCCRB;

Yes, it is and must be so simple. Don't add "static" or "extern" keywords like we usually do for variables in a header file. It will pass compilation and work as a global variable template among all CUs. Same instances will share the same actual variable, i.e., changes in one CU will affect other CUs, as long as they have the same template arguments.

As we know, if you define a variable with no necessary keywords like "static" or "extern" in a header file, it will cause a redefinition error if the header is included in more than one CUs. "static" tell the compiler to copy this variable for each CU as individual variables, so changes in one CU won't affect that in another CU. Instead, "extern" tells the compiler that this variable here is only a declaration. It's defined and owned in only one of the CUs, and other CUs should only keep a reference or symbol to it. So that changes in one CU WILL affect other CUs.

However, a variable template is neither a static definition nor an extern declaration. It's a special instruction to the compiler: find all cases of references to this template, combine those of the same template arguments, and generate one definition automatically for each unique instance! This time the compiler takes the responsibility to avoid redefinition: it scans all CUs and then generates unique definitions itself!

This automatic definition is so convenient if you don't want to give the variable an initial value, and if there are too many possible instances to be listed one by one. However, if you do want an initial value, you'll have to define it yourself, and you'll need an individual CU to own these variables to avoid redefinition:

//In the header file:
template <int TimerCode>
extern int TCCRA;
//In the CPP file:
template <>
int TCCRA<1> = 2;
template <>
int TCCRA<2> = 5;
//Naturally you have to provide initial values for all possible instances …

For this case, the "extern" keyword is necessary because you explicitly defined all valid instances in your specially provided CU, and other CUs must refer to this definition. The compiler shouldn't generate a random definition itself. This is very much like a normal global variable definition, only adding some template grammars. However, the user can only use instances provided in your CPP file. This is also very natural because only for known instances can you provide an initial value.

I found very few tutorials on this topic on the Internet. I hope my experience will help more people ~

  • Thanks! I've arrived at similar code, but yeah, this stuff is hard to search for and various compilers are picky. Most results think you are talking about C++11 extern templates. – Thomas Eding Jul 20 '22 at 01:12
1

The standard defines many global constants which are templatized such as the type traits type_trait_v constants.

They are defined as such in the header

template<class T>
inline constexpr some_type some_variable = ...;

In your example you could do something like

template<uint8_t Pin>
inline constexpr uint8_t BitMask = digitalPinToMask(Pin);

For this to work then digitalPinToMask must be invocable in a constexpr context.

Mestkon
  • 3,532
  • 7
  • 18
  • Unfortunately it's a PUBLIC GLOBAL VARIABLE that could be changed by random users of this library, in any number of compile units supplied by the user. – 埃博拉酱 Jul 17 '21 at 04:15
  • In that case I would recommend that you reevaluate your design – Mestkon Jul 17 '21 at 12:18
1

(Not to be confused with C++11 Extern Templates)

Ok, so after a long while battling compilers and linkers, the best solution I came up with for declaring a global variable that is a template with extern linkage is:

// lib.hpp
#pragma once

namespace lib {

template <typename T>
struct _GlobalTemplateExternImpl {
     std::array<T, 2> value;
};

template <typename T>
inline auto &global_template_value = _GlobalTemplateExternImpl<T>::value;

}
// lib.cpp
#include "lib.hpp"

template
struct lib::_GlobalTemplateExternImpl<int>;

template <>
std::array<int, 2> lib::_GlobalTemplateExternImpl<int>::value = {}; // NOTE: the assignment (`= {}`) is important to do

template
struct lib::_GlobalTemplateExternImpl<float>;

template <>
std::array<float, 2> lib::_GlobalTemplateExternImpl<float>::value = { 7.0f, 66.6f };
// main.cpp
#include "lib.hpp"
#include <iostream>

int main() {
    using namespace lib;
    std::cout << global_template_value<int>[0] << "\n";
    std::cout << global_template_value<int>[1] << "\n";
    std::cout << global_template_value<float>[0] << "\n";
    std::cout << global_template_value<float>[1] << "\n";
    return 0;
}

And since C++ needs silly acronyms, I shall dub this the "Implicitly Externed Template Global Pattern" (IETGP) since the keyword extern is nowhere to be found.

Thomas Eding
  • 35,312
  • 13
  • 75
  • 106