Overload Resolution differs between compilers
Asked Answered
C

1

8

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?

Crazy answered 31/5, 2020 at 22:4 Comment(10)
Are they using the same version of the C standard? What happens if you add switches to specify the standard version?Danielldaniella
What version of clang? The latest version prints const.Printery
@EricPostpischil On trunk, all c++ versions print const, on gcc and clang. At least from c++11.Printery
C++14 on both. I edited the question now.Crazy
Can you also run --version and add that result for both compilers?Printery
@Printery the OP specified the versions of both compilers.Venge
@Venge I'm sorry, this is probably silly, but I don't know what the Apple clang number means. What version of clang is it on godbolt, say?Printery
@Printery Afaik, Apple's forks don't correspond exactly to any accepted version.Crazy
Oh, well I can believe that. So I take it there's no way to test the code without my having a mac? As far as I can tell, I can't reproduce your result on any version of clang.Printery
Most likely this is not an overload resolution difference, but implementation of std::pair difference.Innes
F
3

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.

Fathom answered 1/6, 2020 at 0:24 Comment(3)
It's related to the constructor matching problem, as he couldn't create a copy constructor with non constant object, if he comment the definition of copy constructor with constant object the compiler produce error, so std::pairs has nothing to deal with this problem (if you think, could you please elaborate).Richrichara
Look at the error you get, when you remove the const copy constructor. This problem occurs twice. First because you cannot bind a non-const reference to the temporary created by Foo() in OP's first pair constructor. If you fix this (by e.g. creating a named object beforehand) the problem exists just in the second pair constructor. You will see that problem comes from the constructor std::pair(const std::pair&) being called which later needs a const copy constructor for Foo. I would imagine (but cannot test) that there would be no problem on the mac.Fathom
You may be right, I have posted a question target this point I have posted my thoughts here the link, thanks. #62128993Richrichara

© 2022 - 2024 — McMap. All rights reserved.