Jason Yu
Jason Yu

Reputation: 2038

cv-qualifiers and rvalue-reference

Suppose we have this piece of code shown as below, the question is that why the cv qualifier (const) for "c" is not kept which the behavior is distinct from "v"?

int main(int argc, char **argv) {
  int x{};
  int y{};
  const auto [v] = std::tuple<int>(x);
  const auto [c] = std::tuple<int&&>(std::move(y));
  decltype(v) vv = 10;  // vv -> const int;
  decltype(c) cc = 100; // cc -> int&&;
  return 0;
}

Also, can I mimic the same type deduction process with template argument deduction somehow like below?

template<class T> 
void foo(T t) {  // here should be T rather than universal reference;
  // mimic the same behavior as above somehow ...
}

Doubt 2:

For the code as below, it seems the "auto" inference for "Structured Binding" does not align the same rule as the normal usage of "auto"?

What I expect is that for the first "auto", the decltype(v) should be the type of const int rather than int& like the second one since I do not specify a "&" beside "auto. So, any special rules for "Structured Binding" with "auto"?

int main(int argc, char **argv) {
  int x{};
  const auto [v] = std::tuple<int&>(x); // v -> int&;
  static_assert(std::is_same_v<decltype(v), int&>);

  int& rx = x;
  const auto c = rx;  // c -> const int;
  static_assert(std::is_same_v<decltype(c), const int>);

  return 0;
}

Upvotes: 2

Views: 307

Answers (2)

Jeff Garrett
Jeff Garrett

Reputation: 7393

Given

  const auto [v] = std::tuple<T>(x);

The type decltype(v) is T const, i.e. const-qualified T. If T is int, then decltype(v) is int const (which also may be written const int). If T is int&&, then decltype(v) is int&& const which is int&& (not const int&& which is a reference to const int). The type int&& const is the same as int&& because references are always effectively const. The objects they refer to may be mutable, but references themselves in C++ are immutable.

With template type deduction without universal references, you cannot mimic this type transformation (add const to T) as far as I can tell. But there is a type transformation trait std::add_const_t<T>.


Update for Doubt 2

The structured binding

const auto [v] = std::tuple<int&>(x); // v -> int&;

is not analogous to

int& rx = x;
const auto c = rx;  // c -> const int;

It is instead analogous to

const auto e = std::tuple<int&>(x);
auto&& v = std::get<0>(std::move(e));

The const qualification applies to the tuple, not to the binding of v. The reference qualifier or lack of it applies to the tuple. The binding of v is always reference-like.

The oddness is actually in the other case:

const auto [v] = std::tuple<int>(x);

Still v is reference-like, but decltype(v) is int. The difference is that bindings in structured bindings are aliases, not references. They are different names for the things referred to, but don't themselves have reference types.

So:

const auto [v] = std::tuple<T>(x);

is most analagous to:

const auto e = std::tuple<T>(x);
auto&& r = std::get<0>(std::move(e));
(introduce v as a name for that which r refers to)

where that third line is not something we have the ability to write.

Upvotes: 3

brandon_busby
brandon_busby

Reputation: 132

As Jeff Garret mentioned, const will apply to the entire auto type deduced. If the type is int&, for example, then const auto will be the same as auto const, which is the same as int& const. The reference is const, not the data.

Now, what can you do about this? You don't have to stop using auto. You can simply specify const auto&. auto will deduce to int, and the whole type will be const int& (equivalently int const&). This is how you get your data to be const.

Upvotes: 0

Related Questions