Alex Morales
Alex Morales

Reputation: 2791

C++ Weird behavior on vector of pair containing reference

I've just found something really weird, check out this code:

#include <cstring>
#include <cstdio>
#include <utility>
#include <vector>

using namespace std;

class A {
    public:
    int n = 0;
    A(const char* p) { n = strlen(p); };

    A(A const&) = delete;
    void operator=(A const&) = delete;
};
void F(vector<pair<const char*, const A&>> v) {
    printf("F\n");
    for(vector<pair<const char*, const A&>>::iterator it = v.begin();it!=v.end();++it) printf("  '%s': %p %i\n", it->first, &it->second, it->second.n);
};

int main(int, char**) {
    F({
            { "A", "A" },
            { "B", "BB" },
            { "C", "CCC" },
            { "D", "DDDD" }
        });
};

Now compile it with clang++ -std=c++11 -Wall -Wextra -Wpedantic -O0 main.cc -o main, or something similar (with optimization disabled).

And you should see an output like this:

F
  'A': 0x7fff57a0b988 1
  'B': 0x7fff57a0b9d0 2
  'C': 0x7fff57a0ba18 3
  'D': 0x7fff57a0ba60 4

Which is nice, the compiler automatically creates the vector object and the corresponding A& references, which are all different.

Now, compile it with clang++ -std=c++11 -Wall -Wextra -Wpedantic -O1 main.cc -o main, notice how I just added the lowest level of optimization.

And you would see,

F
  'A': 0x7fff5ac54b30 1629262454
  'B': 0x7fff5ac54b30 1629262454
  'C': 0x7fff5ac54b30 1629262454
  'D': 0x7fff5ac54b30 1629262454

that all parameters reference the same A& object, which I find wrong.

Here are my compiler details:

Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix

Is this expected behavior? Is this a compiler bug?

Update: As pointed out by Matt McNabb, vectors and initializer_lists are not designed to work with const references (although, it compiles fine in clang). However, when writing the same function as void F(vector<pair<char*, A&&>>) {} the error still persists.

Upvotes: 3

Views: 333

Answers (3)

Vaughn Cato
Vaughn Cato

Reputation: 64308

This is due to the constructor of a pair that takes forwarding references:

template<class U, class V> pair (U&& a, V&& b);

When creating the initializer_list that is passed to the vector constructor, temporary pairs are created. Each pair is constructed using the initializers provided. For example, when you construct the first pair with

{ "A", "A" }

The compiler finds that the best matching constructor is template <typename U,typename V> pair(U&&,V&&) with U and V being const char (&)[2]. This means that a temporary A is not created at the call site. The temporary A is created internally to the pair constructor:

template<class U, class V> pair (U&& a, V&& b)
: first(std::forward<U>(a)), 
  second(std::forward<V>(b))
  // second is of type const A&, but std::forward<V>(b) is an rvalue
  // of type const char [2]
{
}

Since the argument type (const char (&)[2]) doesn't match the member type (const A &), a temporary is created to initialize the member, but this temporary only lasts until the constructor exits. That means your pair is left with a dangling reference.

One way to avoid this would be to explicitly create the temporaries at the call site:

F({
        { "A", A("A") },
        { "B", A("BB") },
        { "C", A("CCC") },
        { "D", A("DDDD") }
    });

Now the temporaries will last until F returns, and no temporaries will be created inside the pair constructor since the types match.

Note that if you had the C++03 form of std::pair, which doesn't have forwarding references, the behavior would be what you expect. The temporaries would be created inside main and would last until the call to F() returns.

Upvotes: 2

EboMike
EboMike

Reputation: 77762

Your code seems a bit odd. You're doing this:

void F(vector<pair<const char*, const A&>> v) {

So you're expecting a vector with references to A objects. But you don't have any A objects. You're passing it string literals, for which the compiler implicitly creates A objects - but those are temporary, so by the time your function body runs, they are already gone, and referencing them is undefined behavior, which is why it works with -O0, but not -O1.

If you want to implicitly create A objects and then keep them, you can't use references. Try

void F(vector<pair<const char*, const A>> v) {

Example here: http://ideone.com/VNIgal

Upvotes: 4

David Schwartz
David Schwartz

Reputation: 182837

They don't reference the same A& object. They reference different objects that happen to have the same memory address because their lifetimes don't overlap.

There's no particular expectations you should have regarding whether objects with non-overlapping lifetimes do or don't occupy the same memory address.

Upvotes: 1

Related Questions