7

I am wanting to forward declare variable templates in a header file, and then have the actual instantiations in a separate compilation unit.

I was led to believe that C++14 variable templates operated very much like static class variables do. This is unfortunately seeming not to be quite the case, and it is preventing me from forward declaring my variable templates.

template <typename T> struct Variable {
    static int variable;
};

template <typename T> 
extern int variable;

int main() {
    (void) Variable<char>::variable;
    // (void) variable<char>;                   // <-- line 10
}

template <> int Variable<char>::variable = 42;
template <> int variable<char> = 23;

The code sample above compiles and runs as-is under GCC. But uncommenting line 10 gives a compile-time error:

specialization of 'variable<char>' after instantiation
    template <> int variable<char> = 23;
                     ^
Tom Swirly
  • 2,740
  • 1
  • 28
  • 44
  • 1
    Clang rejects both. This is ill-formed NDR. Explicit *instantiation* and explicit *specialization* are completely different beasts. – T.C. Jan 24 '16 at 06:17
  • All right - so how *do* I accomplish what I want - which is to declare the variable in a header but define it in a .cpp? – Tom Swirly Jan 24 '16 at 16:12

2 Answers2

3

I think you're on the right track.

The trick is this: In any one translation unit, don't instantiate the template prior to your specialization.

For example:

// test.h
#ifndef TEST_H
#define TEST_H

template <typename T> 
extern int variable;

template <> extern int variable<char>;
template <> extern int variable<int>;

#endif // TEST_H

Then:

// test2.cpp
#include "test.h"

template <> int variable<char> = 23;
template <> int variable<int> = 24;

And finally:

// test.cpp
#include "test.h"
#include <iostream>

int
main()
{
    std::cout << variable<char> << '\n';
    std::cout << variable<int> << '\n';
}

For me this outputs:

23
24

Update

T.C. points out in the comments below that the specializations need to be declared before first use, so I've updated "test.h" above to do that.

Update 2

There seems to be some implementation divergence. clang appears to handle this fine:

template <typename T> 
extern int variable;

template <> extern int variable<char>;
template <> extern int variable<int>;

#include <iostream>

int
main()
{
    std::cout << variable<char> << '\n';
    std::cout << variable<int> << '\n';
}

template <> int variable<char> = 23;
template <> int variable<int> = 24;

http://melpon.org/wandbox/permlink/DGYKvvoPbmRIHaFi

However gcc gives an error:

prog.cc:4:13: error: explicit template specialization cannot have a storage class
 template <> extern int variable<char>;
             ^~~~~~

prog.cc:5:13: error: explicit template specialization cannot have a storage class
 template <> extern int variable<int>;
             ^~~~~~

I've searched the standard and the Core Issues list, and I can't find anything to indicate one compiler or the other is correct. If someone does see such evidence, I'm happy to include it in this answer.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • That's still [ill-formed NDR](http://eel.is/c++draft/temp.expl.spec#6). What's needed is a declaration of the explicit specialization in the header, though I'm not sure if the OP actually means to explicitly specialize the template in the first place. – T.C. Jan 24 '16 at 18:00
  • @T.C.: Thanks, fixed. – Howard Hinnant Jan 24 '16 at 18:28
  • This still doesn't work - here's the code. https://github.com/rec/variable-template It won't work at all on clang; on g++ 5.3.0, it compiles but prints 0 and 0; a "unity" build also doesn't compile. – Tom Swirly Jan 24 '16 at 19:44
  • Updated it to include the extern statements - now it doesn't compile under anything! – Tom Swirly Jan 24 '16 at 19:49
  • 1
    C++14 [dcl.stc]/1 says "A *storage-class-specifier* shall not be specified in an explicit specialization or an explicit instantiation directive." – M.M Jan 03 '17 at 23:33
0

I also encounter this same issue and found a solution after some research.

TL;DR: Instantiate, not specialize template. Use extern template int variable<int>, not template extern int variable<int>.

Seems extern template is a feature in C++ that make a template function an external symbol rather than compile every time. Usually this is a trick to speed up compile time but in our case it fits well.

So the whole solution would be:

variable.h

// Declare a template variable.
template <typename T> 
extern int variable;

// Implicit instantiation of variable declaration.
extern template int variable<int>;

variable.cpp

// Define a template variable.
template <typename T>
int variable = 42;

// Implicit instantiation of variable definition.
template int variable<int>;

I haven't try for other compilers but it works for clang 10.

Some reference I found for extern template.