Making a tuple hold either a const reference or a const value

I'm trying to make a std::tuple that ends up holding either const references, or a value that was either copied or moved as appropriate where taking a reference wouldn't be sensible (e.g. temporaries).

So far I've got:

#include <functional>
#include <iostream>

template <typename ...Args>
struct foo {
  const std::tuple<Args...> values;
};

template <typename T1, typename T2>
foo<T1, T2> make2(T1&& v1, T2&& v2) {
  return foo<T1,T2>{std::tuple<T1, T2>(std::forward<T1>(v1),std::forward<T2>(v2))};
}

int main() {
  double d1=1000;
  double& d2 = d1;
  auto f = make2(d2, 0);
  std::cout << std::get<0>(f.values) << ", " << std::get<1>(f.values) << "\n";
  d1 = -666;
  std::get<0>(f.values)=0; // Allowed - how can I inject some more constness into references?
  //std::get<1>(f.values) = -1; // Prohibited because values is const
  std::cout << std::get<0>(f.values) << ", " << std::get<1>(f.values) << "\n";
}

Which is close, but not quite const enough for what I was hoping - I end up with a const std::tuple<double&, int> which of course allows me to modify the double that the tuple refers to.

I tried sprinkling some more constness into make2:

template <typename T1, typename T2>
foo<T1 const, T2 const> make2(T1&& v1, T2&& v2) {
  return foo<T1 const,T2 const>{std::tuple<T1 const, T2 const>(std::forward<T1>(v1),std::forward<T2>(v2))};
}

That succeeded in making the int (i.e. non-reference) tuple member const (not terribly exciting given I can make the whole tuple const easily enough), but did nothing to the double& member. Why? How can I add that extra constness?

Upvotes: 1

Views: 571

Answers (1)

R. Martinho Fernandes
R. Martinho Fernandes

Reputation: 234504

It didn't work because T1 const adds top-level const. I.e., it would make double &const, which is not different from double&. You need a inner const: "reference to const T1".

You could build this up with a combination of remove_reference, add_const and add_reference, or just write a small trait that puts the const in the right place:

template <typename T>
struct constify { using type = T; };
// needs a better name

template <typename T>
struct constify<T&> { using type = T const&; };

// and an alias for UX ;)
template <typename T>
using Constify = typename constify<T>::type;

Upvotes: 1

Related Questions