c++gccc++17icc

error: no suitable user-defined conversion from "Data" to "std::__cxx11::string" exists when using icc 17.0


I am getting a compiler error when I am trying to compile with specific compiler version. ie. icc 17.0 with -std=c++17 -O3

Compiler Error:

source>(19): error: no suitable user-defined conversion from "Data" to "std::__cxx11::string" exists
      Data temp{std::forward<Data>(d)};
                ^

compilation aborted for <source> (code 2)
ASM generation compiler returned: 2
<source>(19): error: no suitable user-defined conversion from "Data" to "std::__cxx11::string" exists
      Data temp{std::forward<Data>(d)};
                ^

compilation aborted for <source> (code 2)
Execution build compiler returned: 2

Code:


#include <string>
#include <vector>
#include <iostream>

struct Data {
  std::string id{};
  std::string rowData{};
  int totalRawDataLength{};

  std::vector<int> rawDataOffset{};
  std::vector<int> rawDataLength{};

  Data() = default;
  Data(const Data&d) =default;
  Data(Data &&d) =default;
};

Data ProcessData(Data &&d) {
    Data temp{std::forward<Data>(d)};
    // some code
    return temp;
}

int main() {
  Data d{};
  d.id = "id_001";
  d.rowData = "some data";
  d.rawDataOffset.emplace_back(4);
  d.rawDataLength.emplace_back(4);
  auto x = ProcessData(std::move(d));
  std::cout << "Test:" << x.id << std::endl;
  return 0;
}

Demo

following code works for all version of gcc and it works with the higher version of icc with same compiler options.
it even works for -std=c++11 -O3 for icc 17.0

on further debugging found out that something is wrong with default copy constructor which is being generated.

Code with copyconstructor

I am not able to understand what's wrong happening here, is it some kind of compiler bug which got resolved in later releases?


Solution

  • This is yet another curious example of when the fickle aggregate strikes, here alongside an arguable bug in the ICC compiler (version 17.0).


    Data is an aggregate class in C++14 and C++17 (but not in C++11)

    In C++11, an aggregate class was defined as, as per [dcl.init.aggr]/1 (N3337) [emphasis mine]:

    An aggregate is an array or a class (Clause [class]) with no user-provided constructors ([class.ctor]), no brace-or-equal-initializers for non-static data members ([class.mem]), no private or protected non-static data members (Clause [class.access]), no base classes (Clause [class.derived]), and no virtual functions ([class.virtual]).

    In C++14 and C++17, except for some explicit details in the latter, the rules are mostly the same as in C++11, except that the requirement

    [...] no brace-or-equal-initializers for non-static data members

    has been removed.

    Thus, as your class Data have no user-provided constructors (see [dcl.fct.def.default]/4) and moreover only public data members, it is an aggregate in C++14 and C++17, whereas, due to its default member initializers, it is not an aggregate in C++11.


    ICC wrongly applies aggregate initalization for C++14 and C++17, where Data is an aggregate

    Now, the follow direct-braced-init initialization

    Data temp{std::forward<Data>(d)}
    

    will enumerate constructor overloads in C++11, whereas in C++14 and C++17 it is direct initialization, particularly by the following rule (from cppreference:

    The effects of list initialization of an object of type T are:

    • If T is an aggregate class and the initializer list has a single element of the same or derived type (possibly cv-qualified), the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).

    If the conditions of the bullet above does not apply, direct-brace-init for an aggregate class will be aggregate initialization. This should not happen here, but ICC seemingly wrongly apply aggregate initialization in C++14 and C++17 (where Data is an aggregate class), using std::forward<Data>(d) to direct-initialize the first data member of Data (namely the data member id which is of type std::string) rather than direct-initialization of Data itself; all the other data members are initialized by their associated default member initializers.


    Replacing an explicitly-defaulted constructor with a user-provided one make an aggregate class become a non-aggregate class

    [...] on further debugging found out that something is wrong with default copy constructor which is being generated.

    In the example you show here you have simply provided your own copy-constructor, as compared to explicitly-defaulting it at its first declaration. This means the class Data is no longer an aggregate (in any of C++11 through C++17).


    C++20 sorts out most of the earlier aggregate confusion

    Note that as per C++20 most of the aggregate confusion has been resolved, as per the implementation of P1008R1 (Prohibit aggregates with user-declared constructors), specifically by no longer allowing aggregates to have user-declared constructors, a stricter requirement for a class to be an aggregate than just prohibiting user-provided constructors.