10

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:

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");
Community
  • 1
  • 1
Frank Secilia
  • 124
  • 1
  • 9

1 Answers1

1

You're trying to compare two distinct pointers to class members as constexpr and it isn't specified in standard, if compiler should evaluate it as constexpr (addresses of objects might not be yet known?). MSVC and clang does that, gcc doesn't. The result for comparision of pointer to itself is defined.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • 1
    They're not members. The variables are static. Can you site the standard on this? This sounds a little unreasonable. – Frank Secilia May 14 '17 at 12:59
  • @Frank Secilia they are still static members. That's not equivalent to a global variable (which would work fine in this case). Same happens if you try compare pointers to methods. Standard didn't defined what happens in this case, so it's a little ambigous – Swift - Friday Pie May 14 '17 at 15:46
  • @Swift Same problem with global variable templates: [demo](https://wandbox.org/permlink/QlCuXt1Or0yreg3h). – Oktalist May 14 '17 at 16:00
  • Pointers to methods are definitely not similar. They're almost always represented as strongly-typed ints. Check out the edit I added at the end. That works like I expect. – Frank Secilia May 14 '17 at 16:30
  • @Oktalist constexpr pointer variable must be initialized by an address constant expression, that is the address of an object with static storage duration, the address of a function, or a null pointer value. Question is, is the instantiated template variable actually an object with static duration, (same with static mebers of templates.. they aren't necessary thread-wide, they are created \initialized _before_ the exectution of any function or method in same compilation unit, but when - not defined, it is implementation-specific)? – Swift - Friday Pie May 14 '17 at 18:35
  • "Address constant expression" was removed in C++14 but replaced with equivalent wording about static storage duration. However, it is clear that all the pointers in our examples point to objects with static storage duration. Static storage duration is not the same thing as static initialization. Order of initialization should not matter, the address of an uninitialized variable is well-defined, and anyway the objects in our examples are constant-initialized (initialized at compile-time) so there is no runtime initialization at all. – Oktalist May 14 '17 at 20:17