Reputation: 2791
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
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
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
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