Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different logic produced by clang and gcc for same code. Which is correct?

I have found a discrepancy between the logic produced by gcc-8 and clang-6.

This happened in a real code base, when having developed using clang, I deployed using gcc.

Please kindly advise which compiler is in error so that I can file a bug appropriately.

Synopsis

A is implicitly convertible to B A is constructible from both an A (copy/move) and std::initializer_list<B>.

When initialising an A from an A&&:

  • clang selects the move-constructor
  • gcc selects the initializer_list constructor.

live demonstration: https://coliru.stacked-crooked.com/a/bc50bd8f040d6476

MCVE

#include <initializer_list>
#include <utility>
#include <iostream>

struct thing;

struct thing_ref
{
    thing_ref(thing&& other) : ref_(other) {}
    thing_ref(thing& other) : ref_(other) {}

    thing& ref_;
};

struct thing
{
    thing() {}

    thing(std::initializer_list<thing_ref> things)
    {
        std::cout << "initializer_list path\n";
    }

    thing(thing&& other)
    {
        std::cout << "move path\n";
    }

    thing(thing const& other)
    {
        std::cout << "copy path\n";
    }
};

struct foo
{
    foo(thing t) : mything { std::move(t) } {}
    thing mything;
};

int main()
{
    thing t;

    auto f = foo { std::move(t) };
}

Compiler settings:

Nothing special, as per the coliru link: -std=c++17 -O2

like image 524
Richard Hodges Avatar asked Feb 01 '19 09:02

Richard Hodges


People also ask

Is GCC and Clang the same?

Clang is designed as an API from its inception, allowing it to be reused by source analysis tools, refactoring, IDEs (etc) as well as for code generation. GCC is built as a monolithic static compiler, which makes it extremely difficult to use as an API and integrate into other tools.

Is GCC better than Clang?

GCC supports more traditional languages than Clang and LLVM, such as Ada, Fortran, and Go. GCC supports more less-popular architectures, and supported RISC-V earlier than Clang and LLVM. GCC supports more language extensions and more assembly language features than Clang and LLVM.

Do you need GCC and Clang?

You don't need GCC to use Clang, as can be shown in the case of FreeBSD (they completely replaced GCC with Clang/LLVM and don't install GCC in the base anymore for licensing reasons). There are a variety of different C compilers other than GCC, it's just that GCC is the most common.

Does Clang use GCC?

Clang is compatible with GCC. Its command-line interface shares many of GCC's flags and options. Clang implements many GNU language extensions and compiler intrinsics, some of which are purely for compatibility.


1 Answers

Standard draft (T is thing) [dcl.init.list]:

List-initialization is initialization of an object or reference from a braced-init-list. ...

List-initialization of an object or reference of type T is defined as follows:

  • If the braced-init-list contains a designated-initializer-list [does not apply]

  • If T is an aggregate class and [does not apply]

  • Otherwise, if T is a character array [does not apply]

  • Otherwise, if T is an aggregate [does not apply]

  • Otherwise, if the initializer list has no elements [does not apply]

  • Otherwise, if T is a specialization of std::initializer_­list<E> [does not apply]

  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution [applies]

  • ...

[over.match.list]:

When objects of non-aggregate class type T are list-initialized such that [dcl.init.list] specifies that overload resolution is performed according to the rules in this subclause, overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T and the argument list consists of the initializer list as a single argument. [applies]

  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted. [does not apply]

Back to [dcl.init.list] to find out what an initializer-list constructor is:

A constructor is an initializer-list constructor if its first parameter is of type std::initializer_­list<E> or reference to possibly cv-qualified std::initializer_­list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments ([dcl.fct.default]).

There is also a handy note, that reasserts the conclusion:

Note: Initializer-list constructors are favored over other constructors in list-initialization

My conclusion:

The initializer-list constructor candidate should be considered first, and used if it is valid. As thing implicitly converts to thing_ref, it should be valid. It appears to me that GCC is conforming.

If you want to initialize an object of type that has an initializer-list constructor, but don't want to use that constructor, then don't use list initialization i.e. don't use a brace-init-list.

like image 156
eerorika Avatar answered Oct 06 '22 00:10

eerorika