Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is std::is_const::value 'false' even though T's value_type is const?

Tags:

#include <type_traits>  struct foo; int main() {     const foo *bar;      static_assert(std::is_const<decltype(*bar)>::value,                   "expected const but this is non-const!"); } 

This results in a failing static_assert which is unexpected. This is somewhat similar to this question on const references but not quite the same.

In my case, dereferencing bar should give an instance of const foo as its type but yet std::is_const is saying otherwise.

like image 627
greatwolf Avatar asked Feb 02 '17 07:02

greatwolf


1 Answers

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) is const foo& rather than const 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 &.

like image 197
skypjack Avatar answered Sep 27 '22 23:09

skypjack