Bug 82840 - call to conversion operator instead of converting constructor in c++17 during overload resolution
Summary: call to conversion operator instead of converting constructor in c++17 during...
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 8.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: wrong-code
Depends on:
Blocks:
 
Reported: 2017-11-05 01:03 UTC by Bruno Bugs
Modified: 2021-12-10 12:23 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Bruno Bugs 2017-11-05 01:03:48 UTC
This code compiled by gcc trunk with std=c++17 returns 1 
instead of the expected 0 since D::operator C() is called instead of the apparently better match C::C(const D&). Compiling the code with std=c++14
make this behaviour disappear and 0 is returned as expected.

gcc 7.2 also has this behaviour but 6.4 do not.
clang trunk also has this behaviour.

Rationale for why 0 should be returned instead of 1 :
(copied from the original post on stackoverflow)

Tentative reading of the standard (latest draft N4687) :

C c(d) is a direct-initialization which is not a copy elision ([dcl.init]/17.6.1). [dcl.init]/17.6.2 tells us that applicable constructors are enumerated and that the best one is chosen by overload resolution. [over.match.ctor] tells us that the applicable constructors are in this case all the constructors.

In this case : C(), C(const C&) and C(const D&) (no move ctor). C() is clearly not viable and thus is discarded from the overload set. ([over.match.viable])

Constructors have no implicit object parameter and so C(const C&) and C(const D&) both take exactly one parameter. ([over.match.funcs]/2)

We now go to [over.match.best]. Here we find that we need to determine which of these two implicit conversion sequences (ICS) is better. The ICS of C(const D&) only involves a standard conversion sequence, but the ICS of C(const C&) involves a user-defined conversion sequence.

Therefore C(const D&) should be selected instead of C(const C&).


See the discussion on stackoverflow:
https://stackoverflow.com/questions/47110853/call-to-conversion-operator-instead-of-converting-constructor-in-c17-during-ov


static int ret;
struct D;
struct C {
    C() {}
    C(const C&) {}
    C(const D&) {}
};
struct D {
    operator C() { ret = 1; return C();}
};

int main(){
    D d;
    C c(d);
    return ret;
}


g++ -v :

Using built-in specs.
COLLECT_GCC=/home/bruno/software/gcc-svn-install/bin/g++
COLLECT_LTO_WRAPPER=/home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc-svn/configure --prefix=/home/bruno/software/gcc-svn-install
Thread model: posix
gcc version 8.0.0 20171104 (experimental) (GCC) 
COLLECT_GCC_OPTIONS='-v' '-O0' '-g' '-std=c++17' '-Wall' '-Wextra' '-Wpedantic' '-o' 'bug_DoperatorC_CconstDref_minimal' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE bug_DoperatorC_CconstDref_minimal.cpp -quiet -dumpbase bug_DoperatorC_CconstDref_minimal.cpp -mtune=generic -march=x86-64 -auxbase bug_DoperatorC_CconstDref_minimal -g -O0 -Wall -Wextra -Wpedantic -std=c++17 -version -o /tmp/cc2hfg03.s
GNU C++17 (GCC) version 8.0.0 20171104 (experimental) (x86_64-pc-linux-gnu)
	compiled by GNU C version 8.0.0 20171104 (experimental), GMP version 6.1.2, MPFR version 3.1.6, MPC version 1.0.3, isl version none
GGC heuristics: --param ggc-min-expand=30 --param ggc-min-heapsize=4096
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../../x86_64-pc-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../../include/c++/8.0.0
 /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../../include/c++/8.0.0/x86_64-pc-linux-gnu
 /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../../include/c++/8.0.0/backward
 /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/include
 /usr/local/include
 /home/bruno/software/gcc-svn-install/include
 /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
GNU C++17 (GCC) version 8.0.0 20171104 (experimental) (x86_64-pc-linux-gnu)
	compiled by GNU C version 8.0.0 20171104 (experimental), GMP version 6.1.2, MPFR version 3.1.6, MPC version 1.0.3, isl version none
GGC heuristics: --param ggc-min-expand=30 --param ggc-min-heapsize=4096
Compiler executable checksum: a6d84215658bcb7c80db361d04a47f0c
COLLECT_GCC_OPTIONS='-v' '-O0' '-g' '-std=c++17' '-Wall' '-Wextra' '-Wpedantic' '-o' 'bug_DoperatorC_CconstDref_minimal' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 as -v --64 -o /tmp/ccaD0Gbb.o /tmp/cc2hfg03.s
GNU assembler version 2.29.1 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.29.1
COMPILER_PATH=/home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/:/home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/:/home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/:/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/:/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/
LIBRARY_PATH=/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/:/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../../lib64/:/lib/x86_64-linux-gnu/:/lib/../lib64/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib64/:/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-O0' '-g' '-std=c++17' '-Wall' '-Wextra' '-Wpedantic' '-o' 'bug_DoperatorC_CconstDref_minimal' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/collect2 -plugin /home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/liblto_plugin.so -plugin-opt=/home/bruno/software/gcc-svn-install/libexec/gcc/x86_64-pc-linux-gnu/8.0.0/lto-wrapper -plugin-opt=-fresolution=/tmp/ccu9eKoi.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o bug_DoperatorC_CconstDref_minimal /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/crtbegin.o -L/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0 -L/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../../../lib64 -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib64 -L/home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/../../.. /tmp/ccaD0Gbb.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /home/bruno/software/gcc-svn-install/lib/gcc/x86_64-pc-linux-gnu/8.0.0/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-O0' '-g' '-std=c++17' '-Wall' '-Wextra' '-Wpedantic' '-o' 'bug_DoperatorC_CconstDref_minimal' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
Comment 1 Andrew Pinski 2021-12-10 12:01:55 UTC
Hmm, clang also returns 1 for -std=c++17 also.
MSVC and ICC both return 0 for -std=c++latest.
Comment 2 Andrew Pinski 2021-12-10 12:08:20 UTC
This is interesting:

struct D;
struct C {
    constexpr C(int t) : t(t) {}
    constexpr C(const C&) {}
    constexpr C(const D&) {}
    int t = 0;
};
struct D {
    constexpr operator C() { return C{1};}
};
  constexpr  D d;
  constexpr   C c(d);
  static_assert (c.t == 0);
extern "C" void abort (void);
int main(){
    D d;
    C c(d);
    constexpr D d1;
    C c1(d1);
    if (c1.t != c.t)
      abort ();
    return c.t;
}


---- CUT ----
I would have suspected the static_assert to hit and the program to run at runtime without any problems.
Comment 3 Jonathan Wakely 2021-12-10 12:18:27 UTC
GCC changed behaviour for C++1z mode at r240889:

    Further P0135 refinement.
    
            * call.c (build_user_type_conversion_1): Consider conversions from
            a single element in an initializer-list.
            (build_temp): Undo early_elide_copy change.
            (build_over_call): Check that we don't try to copy a TARGET_EXPR
            in C++17 mode.  Set user_conv_p here.
            (convert_like_real): Not here.
            (check_self_delegation): Split out from...
            (build_special_member_call): ...here.  Handle C++17 copy elision.
            * cvt.c (early_elide_copy): Remove.
            (ocp_convert): Undo early_elide_copy change.
            * except.c (build_throw): Likewise.
            * init.c (expand_default_init): Likewise.
            * typeck.c (cp_build_modify_expr): Likewise.

That refers to the guaranteed copy elision paper: https://wg21.link/p0135r1