C++ static template class member variable: non-inline external definitions are not permitted in C++ header units

58 Views Asked by At

I wanted to start a new project that uses modules, and therefore had to put header-only libraries in a header unit. One of them (sol2) triggered an error (the one in the title) when compiling with Clang. I was able to reproduce the error on a smaller header file, and it turns out that the error only occurs when compiling with Clang: GCC doesn't see the issue when compiling the file as a header unit (the code will be below).

Now, I know that the header unit support in both compilers is experimental. My question is: when it is finished, which behavior will be correct? I was under the impression that static template class member variables should be automatically marked inline, but it looks like Clang disagrees? Am I wrong to expect that variable to be considered inline here, or is Clang wrong?

The test header file in question:

#pragma once

template <typename T> struct S { static int v; };
template <typename T> int S<T>::v = 10;

template <typename T> bool b() {
    bool b1 = S<T>::v == 10;
    return b1 && true;
}

inline bool B = b<int>();

Precompiled into a header unit with

[clan]g++ -std=c++23 -fmodule-header -xc++-user-header temp.hh

GCC emits no errors. Clang emits:

./temp.hh:5:33: error: non-inline external definitions are not permitted in C++ header units
5 | template <typename T> int S<T>::v = 10;
  |                                 ^
./temp.hh:8:21: note: in instantiation of static data member 'S<int>::v' requested here
8 |     bool b1 = S<T>::v == 10;
  |                     ^
./temp.hh:12:17: note: in instantiation of function template specialization 'b<int>' requested here
12 | inline bool B = b<int>();
   |                 ^
1 error generated.
1

There are 1 best solutions below

2
GregTheMadMonk On

No, this member variable is not inline. A similar thing happens with function templates, that are not implicitly inline either (see Does it make any sense to use inline keyword with templates?). A simple modification to a header file reveals a potential multiple definition error:


    #pragma once
    
    template <typename T>
    struct S { static int i; };
    
    template <typename T> int S<T>::i = 10;
    template <> int S<int>::i = 10;

And yay

    /usr/bin/ld: /tmp/ccLUB00S.o:(.data+0x0): multiple definition of `S<int>::i'; /tmp/ccAQtrFM.o:(.data+0x0): first defined here

P.S. I still can't think of a case where this will work with inline, though. Sure, the linker error transforms into a compiler duplicate initialization error, so how do we use our power now? Either way, the variable is not inline here and Clang is right, and GCC apparently is too, because what headers could be imported is apparently implementation defined:

An importable header is a member of an implementation-defined set of headers that includes all importable C++ library headers (16.5.1.2).

Splendid!