3

Given the code

#include <typeinfo>
#include <type_traits>

struct A {};
struct B {};

static_assert(&typeid(A), ""); // Fine
static_assert(&typeid(B), ""); // Fine
static_assert(&typeid(A) != nullptr, ""); // Fine
static_assert(&typeid(B) != nullptr, ""); // Fine

constexpr auto const * tA = &typeid(A);
constexpr auto const * tB = &typeid(B);
using T = decltype(tA);
using T2 = decltype(tB);
static_assert(std::is_same<T, T2>::value, ""); // Fine, identical types!
static_assert(tA == tA, ""); // Fine (comparing values of identical type)
static_assert(tB == tB, ""); // Fine (comparing values of identical type)
static_assert(tA != tB, ""); // Error: comparing values of identical type
                             //        suddenly not constexpr?

I get the following error with Clang:

$ clang++ -std=c++1z test.cpp -o test
test.cpp:19:18: error: static_assert expression is not an integral constant expression
static_assert(tA != tB, ""); // Error: comparing values of identical type not constexpr?
            ~~~^~~~~
1 error generated.

And GCC:

$ g++-6.3.0 -std=c++1z test.cpp -o test
test.cpp:19:1: error: non-constant condition for static assertion
static_assert(tA != tB, ""); // Error: comparing values of identical type not constexpr?
^~~~~~~~~~~~~
test.cpp:19:18: error: '(((const std::type_info*)(& _ZTI1A)) != ((const std::type_info*)(& _ZTI1B)))' is not a constant expression
static_assert(tA != tB, ""); // Error: comparing values of identical type not constexpr?
            ~~~^~~~~

It doesn't matter if I use void instead of auto for tA and tB. Only GCC-s output changes slightly:

$ g++-6.3.0 -std=c++1z test.cpp -o test
test.cpp:19:5: error: non-constant condition for static assertion
    static_assert(tA != tB, ""); // Error: comparing values of identical type not constexpr?
    ^~~~~~~~~~~~~
test.cpp:19:22: error: '(((const void*)(& _ZTI1A)) != ((const void*)(& _ZTI1B)))' is not a constant expression
    static_assert(tA != tB, ""); // Error: comparing values of identical type not constexpr?
                ~~~^~~~~

Can someone please explain why only the last static_assert fails to compile, whereas the others do compile AND pass?

jotik
  • 17,044
  • 13
  • 58
  • 123
  • 3
    It seems to me that the reference returned by `typeid` is not constexpr, therefore the returned value is not constexpr. Hesitating a guess as to why the previous calls are treated as compile-time equal, I would way that the compiler is realising that the two non-constexpr pointers refer to the same object. And they are manifestly not nullptr (because they cannot be - no valid address can be equal to nullptr) http://en.cppreference.com/w/cpp/language/typeid – Richard Hodges Sep 21 '16 at 10:46
  • 1
    @RichardHodges and yet you can `static_assert` their addresses : http://stackoverflow.com/a/29288244/1870760 – Hatted Rooster Sep 21 '16 at 10:49
  • @GillBates isn't that the same test? It's static_asserting that two the addresses of two static-storage objects are the same. If they are the same object then they will be - at compile time. – Richard Hodges Sep 21 '16 at 10:51
  • @RichardHodges Indeed, I missed that. you're right. – Hatted Rooster Sep 21 '16 at 10:52
  • @RichardHodges `typeid` does not return a reference. It's an lvalue. And it can be used in constant expressions. – Columbo Sep 21 '16 at 13:06
  • @Columbo This is taken from the cppreference site: "The typeid expression is lvalue expression **which refers to an object with static storage duration**, of the polymorphic type `const std::type_info` or of some type derived from it". It seems to me that it is saying that the lvalue expression is a reference. What else could it be? – Richard Hodges Sep 21 '16 at 13:43
  • @RichardHodges Do you know the difference between a reference and an expression that designates a reference? The latter does not have reference type, and the fact that its (expression) value is a reference is completely irrelevant. The expression is not of reference type and "is" not a reference, but an lvalue. Anyway, in our case, `typeid` expressions are lvalues and nothing prevents them from appearing in constant expressions. – Columbo Sep 21 '16 at 13:45
  • @Columbo apparently I did not. Thank you. – Richard Hodges Sep 21 '16 at 13:52
  • 1
    @Columbo having read through the 2015 draft standard I don't see any mention of this non-reference-reference type of which you speak (section 5.2.8). I merely see the words "l-value which refers to an object of static storage duration". If I were interpreting this to build a compiler I would expect that to mean a reference. Is there some other text elsewhere that clarifies this? – Richard Hodges Sep 21 '16 at 14:04

1 Answers1

2

The compilers seem to adhere to [expr.const]/(2.20):

— a relational (5.9) or equality (5.10) operator where the result is unspecified;

Perhaps pointers to two such objects are not necessarily specified to be unequal, but it appears to be a bug to me, nonetheless.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • I don't understand this answer. – jotik Feb 02 '17 at 20:10
  • @jotik [expr.const]/2 lists conditions under which an expression is not a core constant expression. Your code compares two addresses where it isn't clear whether they are distinct or not, hence the quoted section applies. – Columbo Feb 02 '17 at 20:12
  • @jotik Because it isn't specified that they are or aren't distinct. However, I think that it should be specified that they are distinct, which is why I reckon the implementation's behaviour should be adjusted accordingly. – Columbo Feb 02 '17 at 20:27
  • Sorry, I still don't quite make the connection of how that answers my original question. – jotik Feb 02 '17 at 20:50
  • @jotik Your question is why that expression is not a constant expression. I just explained in detail, with a citation, why that expression isn't a constant expression, and hence both compilers error out at the static assertion. Can you be a *bit* more specific about where exactly you can't follow? – Columbo Feb 02 '17 at 22:54
  • I don't understand why that one expression is not a constant expression and while the other expressions in my example are. What is the fundamental difference between those expressions? I mean the types of the operands don't change, only the values. This doesn't surely mean that whether an expression is a constant expression also depends on the **values** of the operands? – jotik Feb 02 '17 at 23:01
  • @jotik That's the whole point. A constant expression must be evaluated... – Columbo Feb 02 '17 at 23:04
  • Well, the obviously the compilers can evaluate `&typeid(A)`, `!&typeid(A)`, `&typeid(A) != nullptr`, `&typeid(A) == nullptr`, `&typeid(A) != static_cast(nullptr)`, `&typeid(A) == &typeid(A)` and `&typeid(A) != &typeid(A)`. Why can't they evaluate `&typeid(A) != &typeid(B)`? – jotik Feb 02 '17 at 23:13
  • @jotik You realize that in the cases where you compared non-null values, it was the address of the same object? How would you know whether `typeid` values corresponding to two different types are distinct objects? – Columbo Feb 02 '17 at 23:18
  • I don't see why `&typeid(A)` and `&typeid(B)` would point to the same object. But even if they would, I don't see why that matters, because it would only change the result of the comparison (`true` or `false`). – jotik Feb 03 '17 at 00:17
  • @jotik The idea is that constant expressions are evaluated at compile time, so certain operations must be inhibited in order for that to be consistent across implementations. And unspecified results are problematic in that respect, which is why the standard disallows them in constant expressions. Regardless, the wording is right there, so why argue? – Columbo Feb 07 '17 at 12:39