Are variable templates declared in a header, an ODR violation?

What happens when a header file contains a template variable like the following:

template <class T>
std::map<T, std::string> errorCodes = /* some initialization logic */;

Is this variable safe to use? Doing some research on this I found that:

  1. Templates are implicitly extern but that does cause ODR violations
  2. Without any static or inilne or constexpr decoration I can end up with multiple definitions
  3. Keyword inline of C++17 changes the rules but c++20 has a different behavior(?)

So which one is it?


Solution 1:

Templates get an exception from the one-definition rule, [basic.def.odr]/13:

There can be more than one definition of a [...] templated entity ([temp.pre]) [...] in a program provided that each definition appears in a different translation unit and the definitions satisfy the following requirements.

There's a bunch of requirements there, but basically if you have a variable template in a header (a variable template is a kind of templated entity) and just include that header from multiple translation units, they will have the same token-for-token identical definition (because #include), so having more than one definition is fine.

This is the same reason that you don't need to declare function templates inline in headers, but do need to declare regular functions inline.


In C++17, this wording read:

There can be more than one definition of a class type, enumeration type, inline function with external linkage ([dcl.inline]), inline variable with external linkage ([dcl.inline]), class template, non-static function template, static data member of a class template, member function of a class template, or template specialization for which some template parameters are not specified ([temp.spec], [temp.class.spec]) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements.

Note that variable template is not in that list, which was just an omission (it was very much always intended to work). This was CWG 2433, adopted in 2019, but as a defect report (DR) so I wouldn't consider it as counting as a C++20 change. This is the DR that introduced the "templated entity" bullet I cited earlier (rather than listing out several different kinds of templated entity manually, and missing one).

Solution 2:

Is this variable safe to use?

Yes you can declare variable templates this way in headers and then use them in other translation units. One example is given below:

header.hpp

#ifndef HEADER_H
#define HEADER_H
#include <map>
template <class T>
std::map<T, std::string> errorCodes{}; //zero initialized value. Also, note there is no need to use keyword inline here 

#endif

print.hpp

#ifndef PRINT_H
#define PRINT_H

void print();

#endif 

print.cpp

#include "print.hpp"
#include "header.hpp"
#include <iostream>
void print()
{
    std::cout << errorCodes<int>.at(15) << std::endl; // This is safe: prints SomeString
}

main.cpp


#include <iostream>
#include "print.hpp"
#include "header.hpp"
int main()
{
    errorCodes<int>[15] = "SomeString"; //This is safe 
    print();
}

Note in the above program you don't need to use the keyword inline in header.hpp while defining the variable template errorCodes. The C++ compilation system will take care of that.

The output of the above program can be seen here.