1

The below code snippet compiles with a very important warning.

#include <map>
#include <vector>

template <typename iterator>
const std::pair<int, float> &foo(iterator it) {
  return *it;
}

int main() {
  std::vector<std::pair<int, float>> vector;
  std::map<int, float> map;
  vector.push_back(std::make_pair(0, 0.0));
  map.insert(std::make_pair(0, 0.0));
  const std::pair<int, float> &r1 = foo(vector.begin());
  const std::pair<int, float> &r2 = foo(map.begin());
  if (r1 != r2) {
    return 1;
  }
  return 0;
}

There is an implicit conversion from std::pair<const int, float> to std::pair<int, float> during foo(map.begin()) that creates a dangling reference.

ref2.cpp: In instantiation of ‘const std::pair<int, float>& foo(iterator) [with iterator = std::_Rb_tree_iterator<std::pair<const int, float> >]’:
ref2.cpp:16:52:   required from here
ref2.cpp:7:11: warning: returning reference to temporary [-Wreturn-local-addr]
   return *it;
           ^~

We could adjust the type of r2 to std::pair<const int, float> in this case. Nevertheless, it would be useful, in the general case, to assign the results of the two calls to foo() to type-compatible references. For example, the call to foo() might be wrapped in another function that always returns std::pair<int, float>&.

Can the reference assignment be made to operatate in a way that works around the misalignment of const modifiers?

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
brainchild
  • 754
  • 1
  • 7
  • 21
  • Why not just use `auto`? – Kerrek SB Jan 19 '17 at 22:48
  • 1
    @KerrekSB Functions returning references are a big exception to that rule. –  Jan 19 '17 at 23:01
  • @hvd Strictly speaking, it's not really an exception. Binding a prvalue to a reference extends the prvalue's lifetime to the lifetime of the reference. So the function first binds the prvalue to a reference inside the function, and then copies that reference out of the function to a second reference in calling scope. But the first reference dies when the function exits, and therefore so does the prvalue, the second reference dangles. But yes, in spirit it is. – Nir Friedman Jan 19 '17 at 23:09
  • 1
    @KerrekSB Inside the function `foo`, depending on the concrete type of `iterator`, either `*it`'s type matches and a reference to it is returned directly, or a temporary is created and initialised from `*it`, and that temporary is bound to the reference that gets returned. "This is not a "dangling reference"" is not right. When a temporary is created (as in the OP's use of it), there certainly is a dangling reference in there, and the compiler is right to warn for it. –  Jan 19 '17 at 23:25
  • @hvd: Ah, yes, of course. I misread the code. This is indeed bad. – Kerrek SB Jan 19 '17 at 23:58

2 Answers2

2

Edit

The question is really about making std::pair<K,V> work with std::pair<const K,V>; vector<> and map<> are red-herrings. (In particular, see the discussion here about why the key in std::map<> is const.)

Better sample code might be:

#include <vector>

template <typename iterator>
const std::pair<const int, float>& bar(iterator it)
{
    return *it;
}

int main()
{
    const std::vector<std::pair<const int, float>> v1{ std::make_pair(0, 0.0f) };
    bar(v1.begin());

    const std::vector<std::pair<int, float>> v2{ std::make_pair(0, 0.0f) };
    bar(v2.begin());

    return 0;
}

According to your comments, what you're really trying to figure out is how to make the std::map<> iterator work like std::vector<>; the result should be a std::pair<> in both cases, not std::pair<const int, ...>.

With that, I've written this hack; I'm sure it's got problems and/or could be improved:

const auto& remove_const(const std::pair<const int, float>& p) {
    return reinterpret_cast<const std::pair<int, float>&>(p); // :-(
}

template <typename iterator>
const std::pair<int, float> &foo(iterator it) {
    return remove_const(*it);
}
Community
  • 1
  • 1
Ðаn
  • 10,934
  • 11
  • 59
  • 95
  • 2
    You should probably use iterator traits for this. – Kerrek SB Jan 19 '17 at 22:50
  • I understand the response, but the oriignal question pertains to the possibility of unifying the return type regardless of template parameter, as opposed to making the return type parameter-dependent. – brainchild Jan 19 '17 at 22:56
1

You might change:

template <typename iterator>
const std::pair<int, float> &foo(iterator it) {
  return *it;
}

to:

template <typename iterator>
decltype(auto) foo(iterator it) {
  return *it;
}

this requires c++14, to stay with c++11 use:

auto foo(iterator it) -> decltype(*it) {   
marcinj
  • 48,511
  • 9
  • 79
  • 100
  • Doesn't setting the return type to `auto` simply defer the type incompatibility to the assignment by the caller? Even if both `r1` and `r2` are also `auto` they will still be different types that need to be converted at the time of comparison, right? – brainchild Jan 19 '17 at 22:51
  • 1
    No, it simply chooses the correct return type for foo - now its always `std::pair&`, but it should be : `std::pair&` and `std::pair&` – marcinj Jan 19 '17 at 22:56
  • also to be strict its not `auto`, but `decltype(auto)`, the difference is important if you would choose `auto` then reference would be removed. – marcinj Jan 19 '17 at 22:59