Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload Resolution differs between compilers

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 image 961
JMC Avatar asked May 31 '20 22:05

JMC


1 Answers

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.

like image 96
n314159 Avatar answered Nov 04 '22 07:11

n314159