4

I have templated class, say:

template <int X>
struct Foo {
  static int get() {return X;}
};

I can of course explicitly instantiate the version that I want:

template class Foo<1>;

I want to generate an error at compile time if a second explicit instantiation is attempted.

template class Foo<1>;
template class Foo<2>; // How to error here at compile time?

Is this possible?

I suspect this will need to use some "redefinition" trick to get the linker to catch this if compilation is done in multiple translation units. I can't for the life of me figure out if this is possible, or how to do it.

If there is a way to do this, does it work without explicit template instantiation?


Context

I'm writing an entirely static class library to manage some hardware on a microcontroller that I'm using. I want to make it easy to change a compile time parameter (X) and am therefore using templates. #define is not acceptable. constexpr won't work, how would you #include a dependent source file?

Specifically, I have an init() function that can only be run a single time and I'm actually using __attribute__((constructor)) to force it to be run for me before main(). If some other user of the library were to inadvertently instantiate a second instance, bad things would happen.

Cameron Tacklind
  • 5,764
  • 1
  • 36
  • 45

2 Answers2

1

You can encapsulate your class template as a private nested class template, then expose as a public member only the instance you want to create :

class Foo {
private:
    template<int X>
    struct BarImpl {
        static int get() { return X; }
    };

public:
    using Bar = BarImpl<1>;
};

Then you can use it like this :

int i = Foo::Bar::get();
//int j = Foo::BarImpl<2>::get(); // Error, BarImpl is private

Also, since you know only one instance of the template will be created, and you know which one, you can (but of course, do not have to) separate the declaration and the definition of the template in a .hpp and a .cpp file like this :

// Foo.hpp
class Foo {
private:
    template<int X>
    struct BarImpl {
        static int get();
    };

    static constexpr int Y = 1;
public:
    using Bar = BarImpl<Y>;
};

// Foo.cpp
template<>
int Foo::Bar::get() {
    return Y;
}

Since X is not accessible via Foo::Bar we have to save the parameter somewhere (here in Y). If you cannot use constexpr you can just make it const. This also has the advantage of naming your parameter instead of just having a "magic value".

Annyo
  • 1,387
  • 9
  • 20
  • Unfortunately this does not work for me. In your example, nothing is preventing `Foo` from having multiple `BarImpl` instantiations. This also doesn't allow someone to specify which instantiation of `BarImpl` they actually want to use, which is one of the main goals for this project. Sidebar: yes, I almost always separate my templated classes into headers and source files with explicit instantiation of the templated version I want to use. See https://stackoverflow.com/questions/115703/storing-c-template-function-definitions-in-a-cpp-file/41292751#41292751 for more. – Cameron Tacklind Mar 06 '19 at 02:49
  • 1
    It's true nothing prevents `Foo` from having multiple `BarImpl` instantiations, but the idea was that the user is not supposed to modify the code you wrote for `Foo`. Though I did not understand that you wanted the user to specify which instantiation of the template they want to use, I thought you were the one to chose one they had to use. I'll try to find something else then. – Annyo Mar 06 '19 at 08:20
1

I’m pretty sure it’s impossible to do this across translation units as you’d have to generate a non-weak symbol from within a template. Within a translation unit it’s easy:

template<int>
struct Foo {
  friend void highlander();
};

It’s ill-formed to instantiate Foo repeatedly in one translation unit because there can be only one definition of highlander. (Conveniently, it’s impossible to ever refer to the function since it’s visible only to ADL and has no parameters.)

You can of course make it ill-formed across translation units too, by giving each specialization a different return type, but there’s no diagnostic required then and you’ll get none in practice.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76