Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr unique id, compiles with clang but not with gcc

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");
like image 340
Frank Secilia Avatar asked May 13 '17 16:05

Frank Secilia


1 Answers

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.

like image 68
Swift - Friday Pie Avatar answered Nov 04 '22 13:11

Swift - Friday Pie