Инициализация шаблона структуры шаблона

Я пытаюсь создать шаблон связанного списка, и он отлично работает для определенных пользователем типов, но для таких фундаментальных типов, как int поведение gcc и clang, различаются.

template<class T>
struct Node {
  Node* next;
  T val;
};

template<class T, class... Args>
Node<T> create(Args... args) {
  return {nullptr, {args...}};
}

int main() {
  create<int>(0);
}

В то время как clang компилирует этот код без проблем, gcc генерирует следующее сообщение об ошибке.

error: не удалось преобразовать '{nullptr, {args # 0}} из' < brace-closeded list list > to 'Node <int>

Пока я знаю, как решить эту проблему, мне все еще интересно, является ли clang слишком разрешительным, и я не могу полагаться на переносимость этого кода, или это ошибка gcc, которая должна быть разрешена когда-то.

Пример: https://godbolt.org/g/9gnvNQ

Ответ 1

Это ошибка GCC.

Во-первых, скобки вокруг скалярного инициализатора (инициализация списка для скалярного типа) разрешены в соответствии с [dcl.init.list]/3.9

В противном случае, если в списке инициализаторов есть один элемент типа E, и либо T не является ссылочным типом, либо его ссылочный тип связан с привязкой к E, объект или ссылка инициализируются из этого элемента (путем инициализации копирования для инициализация списка копий или прямая инициализация для инициализации прямого списка); если для преобразования элемента в T требуется преобразование сужения (см. ниже), программа плохо сформирована. [Пример:

int x1 {2};                         // OK
int x2 {2.0};                       // error: narrowing

- конец примера]

Во-вторых, Node<int> является агрегатом согласно [dcl.init.aggr]/1:

Агрегат - это массив или класс с

  • нет пользовательских, явных или унаследованных конструкторов ([class.ctor]),

  • нет частных или защищенных нестатических элементов данных ([class.access]),

  • нет виртуальных функций, а

  • нет виртуальных, частных или защищенных базовых классов ([class.mi]).

Таким образом выполняется агрегатная инициализация и val инициализируется списком с помощью {args...} рекурсивно в соответствии с [dcl.init.aggr]/4.2:

В противном случае элемент инициализируется с помощью инициализации-инициализации из соответствующего инициализатора-предложения или элементарного или равного-инициализатора соответствующего предложения назначенного-инициализатора. Если этот инициализатор имеет форму присваивания-выражения или = присваивание-выражение, и для преобразования выражения требуется сужающее преобразование, программа плохо сформирована. [Примечание: Если инициализатор сам представляет собой список инициализаторов, элемент инициализируется списком, что приведет к рекурсивному применению правил в этом подпункте, если этот элемент является агрегатом. - конечная нота]

Затем [dcl.init.list]/3.9 снова применяется.

Как вывод, эта инициализация хорошо определена.

Ответ 2

Я считаю, что вам нужно что-то вроде этого:

return {nullptr, T{args...}};

Это явно создает объект T с использованием предоставленных аргументов и работает с любым определяемым пользователем типом, как показано ниже:

template<class T>
struct Node {
    Node* next;
    T val;
};

template<class T, class... Args>
Node<T> create(Args... args) {
    return {nullptr, T{args...}};
}

struct Foo {
    string s;
    int i;
};

int main() {
    auto n = create<Foo>("foo", 42);
    cout << n.val.s << ' ' << n.val.i << endl;
}