Не совсем, но у вас есть (как минимум) два варианта.
Вы можете либо прописать Foo
:
tuple<int, Foo> MakeFoo() {
return {42, Foo{37}}
}
, либо вы можете добавить конструктор к Foo
который заменяет агрегатную инициализацию, которую вы сейчас делаете:
class Foo {
public:
Foo(const Foo&) = delete;
Foo(Foo&&) = default;
Foo(int x) : x(x) {} // <--
int x;
};
tuple<int, Foo> MakeFoo() {
return {42, 37};
}
Но зачем писать return {42, 37}
вместо return {42, {37}}
? И почему добавление конструктора необходимо для этой работы?
Создание кортежа по крайней мере с одним типом только для перемещения означает, что мы не можем использовать прямой конструктор
tuple<Types...>::tuple(const Types &...)
Вместо этого мыдолжен использовать шаблонный конвертирующий конструктор
template<class... UTypes>
tuple<Types...>::tuple(UTypes &&...)
Таким образом, типы двух аргументов будут выведены. Но {37}
- это список инициализаторов, который в этом случае делает второй параметр функции не выводимым контекстом (см. [temp.deduct.type] /5.6). Таким образом, вывод аргумента шаблона завершается неудачно, и конструктор не может быть вызван. Таким образом, вместо этого мы должны написать return {42, 37}
, чтобы позволить успешному выводу.
Кроме того, этот шаблонный конструктор преобразования участвует только в разрешении перегрузки, когда типы аргументов преобразуются в соответствующий элемент кортежатипы. А конвертируемость не учитывает агрегатную инициализацию, поэтому int
не конвертируется в исходный Foo
. Но если мы добавим конструктор преобразования Foo::Foo(int)
, int
теперь можно преобразовать в Foo
, и можно вызвать конструктор tuple
.