I have constructed the following minimal example of my problem:
#include <iostream>
struct Foo {
Foo() {
std::cout << "default" << std::endl;
}
Foo(Foo& f2) {
std::cout << "non-const" << std::endl;
}
Foo(const Foo& f2) {
std::cout << "const" << std::endl;
}
};
int main() {
std::pair<Foo, int> foop0(Foo(), 1);
std::cout << std::endl;
std::pair<const Foo, int>foop1(foop0);
}
On my Ubuntu machine g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 will print out the following:
$ g++ -std=c++14 test.cpp -o test && ./test
default
const
const
However, Apple clang (version 11.0.3 (clang-1103.0.32.62) Target: x86_64-apple-darwin19.4.0) on my Mac will print:
$ g++ -std=c++14 test.cpp -o test && ./test
default
const
non-const
However, it gets worse: If i change the last line to
std::pair<Foo, int>foop1(foop0);
^ removed const
both compilers will give the first output.
Why does this happen?
EDIT: I have now understood why, according to cppreference, std::pair's ctors should be selected as they are by g++. Still doesn't explain clang's weird behaviour here. A non-conforming implementation maybe?
Like already said, probably the implementations of std::pair
for both of them differ. I wrote two very similar implementations of a pair that exhibit exactly your differing behaviors, even without changing the type of the pair: godbolt
#include <iostream>
struct T {
T() {
std::cerr << "default\n";
}
T(T&) {
std::cerr << "non-const\n";
}
T(const T&) {
std::cerr << "const\n";
}
};
// Comment or uncomment to change the behavior.
//#define MAC
template<class First, class Second>
struct pair {
First first;
Second second;
pair(const First& f, const Second& s) : first(f), second(s) {
}
#ifdef MAC
pair(pair<First, Second>& p) : first(p.first), second(p.second) {
std::cerr << "copy Mac-Like\n";
}
#else
pair( pair<First, Second>& p) : pair(p.first, p.second) {
std::cerr << "copy Ubuntu-Like\n";
}
#endif
};
int main() {
T t;
pair<T, int> u1(t, 0);
pair<T, int> u2(u1);
}
Of course, the pairs on mac and ubuntu are both written more reasonably (and standard-conforming) and have the standard-copy-constructor taking a const-reference (which is the reason for both of them using the const variant then). But I guess they handle the copy constructors from pairs with differing but convertible types differently. Finding out what exactly is different would require to compare the stl implementations on both systems.
The Ubuntu variant seems pretty clear to me, there the pair is just taken by const reference in the constructor from a pair of convertible types. When you have a const at any point of your construction chain you will end up with the const copy constructor of T
.
I find the Mac behavior a bit weird since they have to either take the pair by value or by non-const reference (and really, you should not have a copy constructor taking by non-const reference, why should it ever change the thing it copies? This seems like std::auto_ptr
-level weirdness). Maybe they are (trying to be) clever with some kind of "take it by value and then move" thing.
But I think this is non-conforming, since the pair constructor should take all other pairs by const-reference or by rvalue-reference. Since we are copying, it should use the copy constructor, taking a const reference and hence also have a const reference to the pair.first
and by this taking its const copy constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With