21

The following piece of code behaves differently under g++ 4.9.2 and clang++ 3.7.0. Which one is correct? What part in standard is related to this? Thanks.

#include <iostream> using namespace std; struct Base { Base() = default; Base(const Base&) = default; Base(Base&&) = delete; }; struct Derived : Base { }; int main() { const Base& b = true ? Derived() : Base(); } 

g++ accepts it and clang++ gives an error incompatible operand types ('Derived' and 'Base'). See below for details.

[hidden]$ g++ -v Using built-in specs. COLLECT_GCC=/usr/bin/g++ COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.9.2/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux Thread model: posix gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC) [hidden]$ g++ -std=c++11 b.cpp [hidden]$ clang++ -v clang version 3.7.0 (http://llvm.org/git/clang.git 6bbdbba8ec8a7730c68fee94363547dc2dc65b10) Target: x86_64-unknown-linux-gnu Thread model: posix Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/3.4.6 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.9.2 Selected GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.9.2 Candidate multilib: .;@m64 Candidate multilib: 32;@m32 Selected multilib: .;@m64 [hidden]$ clang++ -std=c++11 b.cpp b.cpp:14:24: error: incompatible operand types ('Derived' and 'Base') const Base& b = true ? Derived() : Base(); ^ ~~~~~~~~~ ~~~~~~ 1 error generated. 
18
  • Clang might be expecting you to use an explicit cast Commented Apr 1, 2015 at 1:09
  • @dyp: Chris didn't say that copy-initialization implies using a copy constructor, which is false: if there is a move constructor, the copy constructor may not be needed. He said since there is a copy constructor, copy-initialization should succeed, which is true, either there is a usable move constructor, or there isn't and the copy constructor gets used. Commented Apr 1, 2015 at 1:32
  • @BenVoigt, It's OK, I really was not paying enough attention when it said copy-initialization. Looking further, it should come down to which constructor is picked by overload resolution for Base b = Derived(); Commented Apr 1, 2015 at 1:37
  • 1
    @dyp, Perhaps it's worth letting the Clang guys know about the abnormal error message in this case? It is rather contrived, I'll admit. Commented Apr 1, 2015 at 2:00
  • 4
    Tangential question: is there any way to do what OP was trying to do? (i.e. have a reference bind directly to either a Base() or Derived() depending on the condition, and extend the temporary's lifetime? Commented Apr 1, 2015 at 2:46

1 Answer 1

7

I don't have N3936 handy, but N3797 §5.12 [expr.cond]/3 contains this (emphasis mine):

Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:

  • If E2 is an lvalue: [removed]
  • If E2 is an xvalue: [removed]
  • If E2 is a prvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:
    • if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other:
      E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.

Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed. If neither can be converted, the operands are left unchanged and further checking is performed as described below. If exactly one conversion is possible, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section.

Now to copy-initialize the final Base operand from Derived(), we can look at §13.3.1.3 [over.match.ctor]:

When objects of class type are direct-initialized (8.5), or copy-initialized from an expression of the same or a derived class type (8.5), overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (12.3.1) of that class. The argument list is the expression-list or assignment-expression of the initializer.

Converting constructors are defined as follows in §12.3.1 [class.conv.ctor]:

A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.

Now, if you'll believe me (for the sake of not having to quote more than I have of 13.3) that a prvalue Derived() will cause overload resolution to choose the move constructor (taking Base&&), despite being deleted, this causes the error from Clang.

In conclusion, Clang is correct in issuing an error. As using a deleted function requires a diagnostic, this is a bug in GCC.

Sign up to request clarification or add additional context in comments.

6 Comments

Clang is indeed trying to move it: coliru.stacked-crooked.com/a/6542febd5d884ecc
I am confused, why move constructor is involved here? What does it try to move, and where does it try to move to?
@dyp, That would be my bad. Let me go back and look.
@chris, it still not clear from your answer - maybe it is implied by your answer but I'd rather something more explicit - whether the program is ill-formed or not and which one of gcc and clang is correct.
The text seems to be saying that Derived() is sliced to make a new Base() which is then bound to the reference if that item is selected. This is a surprising behaviour.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.