6

According to this answer, the following code should be compiled without error:

#include <type_traits>

namespace
{

struct A { int i; };

volatile A a{};
static_assert(std::is_volatile< decltype(a) >{});
static_assert(std::is_volatile< decltype(a.i) >{});

}

but there is a hard error:

main.cpp:10:1: error: static_assert failed
static_assert(std::is_volatile< decltype(a.i) >{});
^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Live example with clang 3.6.0.

Is it a clang bug or am I missing something substantial?

ADDITIONAL:

#include <type_traits>

namespace
{

struct A { int i; };

const A a{};
static_assert(std::is_const< decltype(a) >{});
static_assert(std::is_const< decltype(a.i) >{});

}

Exactly the same behaviour for the latter code snippet.

ADDITIONAL:

static_assert(std::is_volatile< std::remove_pointer_t< decltype(&a.i) > >{});

not cause an error.

Community
  • 1
  • 1
Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169

1 Answers1

8

This is the correct behaviour; the current decltype isn't testing what you think it tests. You have to change your decltype if you want to test for volatile-ness.

It is possible to copy a value out and drop volatile-ness. But a reference must be volatile reference-to-volatile.

int foo() {
    int i = a.i;            // volatile not needed
    volatile int &i = a.i;  // volatile needed
}

The decltype is treating a.i as an rvalue, not an lvalue, doing what decltype does by default and is doing a "naive" text analysis and reporting the type of A::i as written in the source code; therefore, decltype(a.i) is int.

Putting () around an expression in decltype changes its behaviour (in good ways usually, for this kind of question), giving us an lvalue when appropriate. Therefore, decltype(( a.i )) is volatile int &.

Now, you might expect that is_volatile< volatile int & >::value would be true. But it's not. We need to remove the reference before we can test for the volatile-ness.

static_assert(std::is_volatile< std::remove_reference_t<decltype((a.i))> >{});

Finally, you might be surprised that volatile int & isn't volatile according to is_volatile. But I guess it's because references are really very like pointers - and the 'pointer' inside a reference isn't volatile, even if the object pointed to is volatile. That's my theory anyway. const int& also does not satisfy is_const.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • 1
    The semantic of reference is almost like *dereferenced pointer*. – Tomilov Anatoliy Jul 31 '15 at 10:46
  • @T.C., my fault. I didn't read my test results correctly. I think I just assumed it would be `is_const` and wasn't expecting the evidence to contradict me. I think it's really weird though! – Aaron McDaid Jul 31 '15 at 10:52
  • Thanks all for the feedback! This answer has changed a lot. It was incorrect at many times, should be reasonably good now. (I'm still not entirely satisfied with it :-) ) – Aaron McDaid Jul 31 '15 at 10:57
  • "[...] and is doing a "naive" text analysis and reporting the type of A::i as written in the source code; therefore, decltype(a.i) is int." Why is it (formally)? – edmz Jul 31 '15 at 11:05
  • @black, most of what I know about `decltype` comes from [the Wikipedia article](https://en.wikipedia.org/wiki/Decltype#Semantics). I'm happy to change it, or for others to change it, if it can be improved; but I'm not a language lawyer. Also, perhaps it's not necessary to fully explain `decltype` here. It should suffice, I think, to tell the OP that unparenthesized doesn't do what the OP expected. – Aaron McDaid Jul 31 '15 at 11:38
  • What about `decltype((a).i)`? – Kerrek SB Aug 01 '15 at 17:47