Morning, folks!
I'm refactoring an event queue. I'm poking around to see if I can make event ids unique at compile time. What I've come up with works with clang 4.0.0, but gives a compile error with g++ 6.3.1.
The idea is to use the address of a static member variable to uniquely identify individual types, then use tagging to generate these unique types from a class template.
Using the address of a static member as a type id is a reasonably common technique, but using templates to do it means being clear of the ODR. MSN cites the standard here to suggest that this is a valid approach: Compile-time constant id
My problem is doing this constexpr. If I remove constexpr and test this at runtime, everything works as expected. Doing this constexpr, however, fails a static assert in g++ saying, "error: non-constant condition for static assertion".
After researching quite a bit, it seems the most similar problems are:
- Pointer arithmetic or calling non-constexpr members: Why is this not a constant expression? There is no pointer arithmetic, everything is constexpr.
- Using reinterpret_cast in a constexpr expression: Constexpr pointer value This is using pointers, but the types are verbatim; so no conversions are applied.
- Using incomplete types: Why is this constexpr static member function not seen as constexpr when called? I'm reasonably sure this type is complete.
Most of these problems are g++ being nonconforming and clang++ erroring out. This is the opposite.
I'm stumped. Here's a stripped-down version of what I've got, annotated with what doesn't compile in g++ in the static asserts:
template <typename tag>
struct t
{
constexpr static char const storage{};
};
template <typename tag>
constexpr char const t<tag>::storage;
struct tag_0 {};
struct tag_1 {};
static_assert(&t<tag_0>::storage == &t<tag_0>::storage, "This always compiles.");
static_assert(&t<tag_1>::storage == &t<tag_1>::storage, "So does this.");
static_assert(&t<tag_0>::storage != &t<tag_1>::storage, "This does not compile with g++.");
static_assert(!(&t<tag_0>::storage == &t<tag_1>::storage), "Neither does this.");
constexpr auto id_0 = &t<tag_0>::storage; // This does.
constexpr auto id_1 = &t<tag_1>::storage; // This does.
static_assert(id_0 != id_1, "This also does not.");
And here are the compiler outputs:
~$ clang++ --version
clang version 4.0.0 (tags/RELEASE_400/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
~$ clang++ -std=c++14 -c example.cpp
~$ g++ --version
g++ (GCC) 6.3.1 20170306
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
example.cpp:14:1: error: non-constant condition for static assertion
static_assert(&t<tag_0>::storage != &t<tag_1>::storage, "This does not compile with g++.");
^~~~~~~~~~~~~
example.cpp:14:34: error: ‘((& t<tag_0>::storage) != (& t<tag_1>::storage))’ is not a constant expression
static_assert(&t<tag_0>::storage != &t<tag_1>::storage, "This does not compile with g++.");
~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
example.cpp:15:1: error: non-constant condition for static assertion
static_assert(!(&t<tag_0>::storage == &t<tag_1>::storage), "Neither does this.");
^~~~~~~~~~~~~
example.cpp:15:15: error: ‘((& t<tag_0>::storage) != (& t<tag_1>::storage))’ is not a constant expression
static_assert(!(&t<tag_0>::storage == &t<tag_1>::storage), "Neither does this.");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cpp:19:1: error: non-constant condition for static assertion
static_assert(id_0 != id_1, "This also does not.");
^~~~~~~~~~~~~
example.cpp:19:20: error: ‘((& t<tag_0>::storage) != (& t<tag_1>::storage))’ is not a constant expression
static_assert(id_0 != id_1, "This also does not.");
~~~~~^~~~~~~
~$
I'm curious why this specific approach doesn't compile with gcc, but does with clang, because this conflicts with how I understand constexpr.
(I'm not asking if this is a good design, or if there are other ways to accomplish this. I have other ways to accomplish this.)
Thanks!
EDIT: A comparable example without templates that does compile with both compilers might be:
struct t1
{
static constexpr int const v{};
};
constexpr int t1::v;
struct t2
{
static constexpr int const v{};
};
constexpr int t2::v;
static_assert(&t1::v != &t2::v, "compiles with both");