Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

compile time check that trait specialization has unique id

Tags:

c++

c++11

I've seen many posts explaining how to generate a unique id for a class.

In my case, the id is chosen by the user (for various reasons), but I want to make sure that no id is used twice in different classes.

I reduced my problem to the following code :

struct A {}; struct B {};

template <typename T> struct traits {};
template <> struct traits<A> { static constexpr size_t id() { return 0; }}
template <> struct traits<B> { static constexpr size_t id() { return 1; }}

Now, is there an easy way for me to make sure that someone does not add a specialization of the trait with a duplicated id :

struct C {};
template <> struct traits<C> { static constexpr size_t id() { return 1; // this should static_assert ! }}

I can use C++11, and I don't want to abuse the pre-processor.

If possible, the solution should not require anything special from the code specializing the trait (i.e if the check can be done externally by looking at already existing specializations, it would be great).

Thanks

like image 735
user1766172 Avatar asked Oct 04 '18 10:10

user1766172


1 Answers

Here is one idea, which I am not sure how applicable it may be for you:

#include <cstddef>

struct A {}; struct B {}; struct C {};

template <size_t Id> constexpr size_t getId() { return Id; }

template <typename T> struct traits {};

template size_t getId<0>();
template <> struct traits<A> { static constexpr size_t id() { return getId<0>(); }};

template size_t getId<1>();
template <> struct traits<B> { static constexpr size_t id() { return getId<1>(); }};

/* This fails to compile
template size_t getId<1>();
template <> struct traits<C> { static constexpr size_t id() { return getId<1>(); }};
*/

int main() { return 0; }

This relies on using explicit template instantiation of a function. The pitfall, obviously, is that you may forget to add this instantiation and still use the function, and then it would not fail to compile. You could define some preprocessor macro to ensure traits are always defined with it.


EDIT: As pointed out by Oliv, the above solution only works when all template instantiations happen in the same translation unit. This version works across compilation units, although it is more prone to mistakes (template parameters and return values must match).

#include <cstddef>

struct A {}; struct B {}; struct C {};

template <size_t Id> constexpr size_t getId();

template <typename T> struct traits {};

template <> constexpr size_t getId<0>() { return 0; }
template <> struct traits<A> { static constexpr size_t id() { return getId<0>(); }};

template <> constexpr size_t getId<1>() { return 1; }
template <> struct traits<B> { static constexpr size_t id() { return getId<1>(); }};

/* This fails to compile
template <> constexpr size_t getId<1>() { return 1; }
template <> struct traits<C> { static constexpr size_t id() { return getId<1>(); }};
*/

int main() { return 0; }

EDIT 2: I posted the question Why does explicit template instantiation not break ODR? due to a behavior I found surprising while writing this answer. See there for more detail on what may or may not fail to compile.

like image 127
jdehesa Avatar answered Sep 28 '22 08:09

jdehesa