16

‒ Ты понимаешь, что происходит?
‒ Тебе объяснить?
‒ Объяснить я и сам могу... Ты понимаешь или нет?

Простите за эпиграф, но... Что-то у меня ощущение, что я могу объяснить, но не понимаю (или наоборот...), что происходит вот тут:

std::vector<int> v;
v.emplace_back(1);
v.emplace_back({1});
v.emplace_back(int{1});

Как стандарт трактует эти три выражения, и почему такой странный диагноз

emplace_back: функция не принимает 1 аргументов

у средней строки?

задан 25 нояб. 2016 в 14:48
4
  • 3
    Так компилятор же всё пишет. Это initializer-list, который не может быть преобразован в int. Commented 25 нояб. 2016 в 14:53
  • С другой стороны, не всё так гладко. Commented 25 нояб. 2016 в 15:03
  • Некое подобие ответа тут. Commented 25 нояб. 2016 в 15:13
  • Ну а что необычного? У средней строки тип initializer_list, а у двух других int godbolt.org/g/YMgcA5 Commented 25 нояб. 2016 в 16:02

1 ответ 1

10

Для этого предложения

v.emplace_back({1});

проблема состоит в том, что когда используются шаблонные параметры, то не выводится тип шаблонного параметра из заключенного в фигурные скобки аргумента. А функция emplace_back использует шаблонные параметы.

Из стандарта C++ (14.8.2.1 Deducing template arguments from a function call)

1 Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list for some P0 and the argument is an initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking P0 as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context

Можно смоделировать такую же ошибку следующим примером объявления класса

#include <iostream>
#include <initializer_list>
struct A
{
 template <typename ...T>
 void f( T &&... ) const {}
};
int main() 
{
 A().f( { 1 } );
 return 0;
}

Компилятор может выдать следующее диагностическое сообщение

prog.cpp: In function 'int main()':
prog.cpp:13:15: error: no matching function for call to 'A::f(<brace-enclosed initializer list>)'
 A().f( { 1 } );
 ^
prog.cpp:8:7: note: candidate: void A::f(T&& ...) const [with T = {}]
 void f( T &&... ) const {}
 ^
prog.cpp:8:7: note: candidate expects 0 arguments, 1 provided

Если вы замените emplace_back на нешаблонный метод push_back , то соответствующий вызов будет компилироваться, так как тип параметра функции известен из типа объявленного инстанциированного вектора и представляет собой тип int.

Поэтому вам явно нужно будет указать тип шаблонного аргумента функции

Например,

#include <iostream>
#include <initializer_list>
struct A
{
 template <typename ...T>
 void f( T &&... ) const {}
};
int main() 
{
 A().f<int>( { 1 } );
 return 0;
}

Или для emplace_back

#include <iostream>
#include <vector>
#include <initializer_list>
int main() 
{
 std::vector<int> v;
 v.emplace_back<int>( { 1 } );
 for ( int x : v ) std::cout << x << ' ';
 std::cout << std::endl;
 return 0;
}

Вывод программы на консоль

1

или для примера, предложенного @alexolut в комментарии

#include <iostream>
#include <vector>
struct S 
{
 S(std::initializer_list<int>) {}
};
int main() 
{
 std::vector<S> v;
 v.emplace_back<std::initializer_list<int>>({1});
}

Что касается выражения в данном вызове

v.emplace_back(int{1});

то здесь используется явное преобразование типов, так называемая функциональная нотация.

Из стандарта C++ (5.2.3 Explicit type conversion (functional notation))

3 Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (8.5.4) with the specified braced-init-list, and its value is that temporary object as a prvalue.

ответ дан 25 нояб. 2016 в 14:55
7
  • Как Вы прокомментируете эту ситуацию? Commented 25 нояб. 2016 в 15:03
  • То есть если я напишу для своего класса конструктор от initializer_list, то это не спасает, потому что этот тип не выводится? (понятно, что я уже пробовал писать :)) Но как тогда компилятор воспринимает - что это там такое лежит {1} - в средней строчке? Какой-то тип эта запись имеет? Commented 25 нояб. 2016 в 15:38
  • Да, я это делал, в смысле - проверял для нешаблонной push_back. Тут вроде понятно - при конструкторе не explicit для пользовательского класса выполняется преобразование через промежуточный тип... Интересно, почему при шаблонном параметре тип initializer_list не выводится - потому что сам шаблон? Commented 25 нояб. 2016 в 15:42
  • @Harry Я сейчас пока удалю свой ответ и займусь чтением стандарта.:) Commented 25 нояб. 2016 в 15:45
  • @Harry Я привел цитату из стандарта в отношении этой проблемы. Commented 25 нояб. 2016 в 16:58

Ваш ответ

Черновик сохранён
Черновик удалён

Зарегистрируйтесь или войдите

Регистрация через Google
Регистрация через почту

Отправить без регистрации

Необходима, но никому не показывается

Отправить без регистрации

Необходима, но никому не показывается

Нажимая «Отправить ответ», вы соглашаетесь с условиями пользования и подтверждаете, что прочитали политику конфиденциальности.

Начните задавать вопросы и получать на них ответы

Найдите ответ на свой вопрос, задав его.

Задать вопрос

Изучите связанные вопросы

Посмотрите похожие вопросы с этими метками.