#include
struct foo;
int main()
{
const foo *bar;
static_assert(std::is_const::value,
\"expected co
Shortly that's because a reference or a pointer to a const type is not a const type.
Note that decltype(*bar) isn't const foo, it's const foo & and they are really different beasts.
Consider the example given here:
std::cout << std::is_const<const int *>::value << '\n'; // false
std::cout << std::is_const<int * const>::value << '\n'; // true
We see that std::is_const<const int *>::value is false and std::is_const<int * const>::value is true.
That's because in const int * the type is pointer to something const, that is not a const type as intended by is_const (and the standard actually). In int * const the const qualifier applies to the pointer type and not to the pointed one, thus the type is a const one, no matter to what it points.
Something similar applies for const foo &, that is a reference to something const.
You can solve using this instead:
static_assert(std::is_const<std::remove_reference_t<decltype(*bar)>>::value, "expected const but this is non-const!");
Or even this, for you don't need to do *bar actually:
static_assert(std::is_const<std::remove_pointer_t<decltype(bar)>>::value, "expected const but this is non-const!");
In this case, by removing the pointer/reference with remove_pointer_t/remove_reference_t your type becomes const foo, that is actually a const type.
As a side note, the example above uses the C++14-ish std::remove_reference_t and std::remove_pointer_t type traits.
You can easily turn those lines of code to C++11 as it follows:
static_assert(std::is_const<typename std::remove_pointer<decltype(bar)>:: type>::value, "expected const but this is non-const!");
It's worth mentioning a few comments to the answer to give more details:
Thanks to @DanielFischer for the question:
Is there a short explanation why
decltype(*bar)isconst foo&rather thanconst foo?
I'm not a language-lawyer, but I guess it can be deduced from [expr.unary.op]/1 (emphasis mine):
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.
And [dcl.type.simple]/4.4 (emphasis mine):
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
Both referring to the working draft.
Thanks to @LightnessRacesInOrbit for the comment. Note that decltype(*bar) being const foo & is a funny C++ quirk of decltype, since *bar is not const foo &.