Не удается скопировать std::vector‹std::function‹void ()›› с помощью юниформ-инициализации. Это правильно?

Следующий код не компилируется в GCC 4.7.2 или Clang 3.2:

#include <vector>
#include <functional>

int main()
{
   std::vector<std::function<void()>> a;
   std::vector<std::function<void()>> b{a};
}

Проблема в том, что компилятор попытается создать b с помощью initializer_list, хотя очевидно, что он должен просто вызвать конструктор копирования. Однако это кажется желаемым поведением, потому что стандарт говорит, что конструкторы initializer_list должны иметь приоритет.

Этот код отлично работает для других std::vector, но для std::function компилятор не может знать, нужен ли вам конструктор initializer_list или другой.

Не похоже, что есть способ обойти это, и если это так, то вы никогда не сможете использовать универсальную инициализацию в шаблонном коде. Что было бы огромным позором.

С другой стороны, Visual Studio (ноябрьская CTP-версия 2012 г.) не жалуется на это. Но поддержка initializer_list на данный момент не очень хороша, так что это может быть ошибкой.


person Malte Skarupke    schedule 30.12.2012    source источник


Ответы (2)


Это LWG 2132, который еще не является отчетом о дефектах, но есть четкий консенсус (и опыт реализации) по его устранению. Стандарт говорит, что конструктор std::function будет принимать любой тип, поэтому, поскольку конструктор списка инициализаторов всегда предпочтительнее других конструкторов, если он жизнеспособный, ваш код пытается создать вектор из std::initializer_list<std::function<void()>> с одним элементом, инициализированным из объекта a. Это затем вызывает ошибку, потому что, хотя вы можете построить std::function<void()> из a, результирующий объект не может быть вызван.

Другими словами, проблема в том, что std::function имеет неограниченный конструктор шаблонов, допускающий преобразование из любого типа. Это вызывает проблему в вашем случае, потому что конструкторы списка инициализаторов предпочтительнее других конструкторов, если они жизнеспособны, а неограниченный конструктор function означает, что всегда можно создать initializer_list<function<void()>> из любого типа, поэтому конструктор списка инициализаторов всегда жизнеспособн.

Предлагаемое разрешение 2132 предотвращает создание std::function из невызываемого типа, поэтому конструктор списка инициализаторов нежизнеспособен, и вместо него вызывается конструктор копии vector. Я реализовал это разрешение для GCC 4.8, и оно уже реализовано в Библиотека Clang libc++ тоже.

person Jonathan Wakely    schedule 30.12.2012
comment
Спасибо за ответ. Ссылка LWG 2132 на самом деле говорит о проблеме, которая похожа, но отличается, и это также решает эту проблему. Это означает, что проблема будет исправлена ​​только для std::function, а не для других типов с шаблонными конструкторами. Я думаю, что мое новое правило для юниформ-инициализации — использовать его везде, где это возможно, за исключением случаев, когда у объекта есть конструктор initializer_list. Затем используйте его, только если вам нужен этот конструктор. Это также означает, что вы не можете использовать его в шаблонном коде. - person Malte Skarupke; 03.01.2013
comment
Я предпочитаю правило не писать неограниченные шаблоны конструкторов, допускающие неявное преобразование из любого типа. Если вы не будете создавать такие типы, вы не вызовете проблем у людей, которые попытаются использовать ваш тип с классами, имеющими конструкторы списка инициализаторов. Стандартная библиотека не следовала этому правилу, но это исправлено для std::function. - person Jonathan Wakely; 03.01.2013

Я не вижу причин, по которым это не должно компилироваться, и оба gcc (версия 4.8.0 20121111) и clang (версия 3.3 (транк 171007)) компилируют код. Тем не менее, «однородная инициализация» далека от единообразной: определенно есть случаи, когда вы не можете использовать фигурные скобки при вызове конструктора.

person Dietmar Kühl    schedule 30.12.2012
comment
g++ 4.7.2 (4.7.2-5ubuntu1) не компилирует код. Очень странное сообщение об ошибке компилятора: pastebin.com/b1mcbYRq - person leemes; 30.12.2012
comment
@leemes, так как вопрос начинается с Следующий код не компилируется в GCC 4.7.2 или Clang 3.2:, я предполагаю, что OP знает об этом (и, вероятно, в первую очередь является причиной вопроса). - person WhozCraig; 30.12.2012
comment
@WhozCraig да, но он не предоставил сообщение компилятора. Вот почему я разместил этот комментарий в первую очередь. - person leemes; 30.12.2012