std :: tuple конструктор по умолчанию с элементом move-constructable - PullRequest
5 голосов
/ 29 октября 2019

Я пытаюсь вернуть std::tuple, содержащий элемент, который не может быть создан для копирования. Это, кажется, мешает мне использовать конструктор класса по умолчанию для создания кортежа. Например, чтобы вернуть кортеж, содержащий Foo, необходимо создать экземпляр foo и std::move d:

class Foo {
  public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) = default;
    int x;
};

tuple<int, Foo> MakeFoo() {
    Foo foo{37};
//  return {42, {37}}; // error: could not convert ‘{42, {37}}’ from ‘’ to ‘std::tuple’
    return {42, std::move(foo)};
}

С другой стороны, если класс определен как конструктор копированияконструкция кортежа работает нормально:

class Bar {
  public:
    Bar(const Bar&) = default;
    int x;
};

tuple<int, Bar> MakeBar() {
    return {42, {37}}; // compiles ok
}

Есть ли способ использовать синтаксис MakeBar с классом Foo?

1 Ответ

2 голосов
/ 30 октября 2019

Не совсем, но у вас есть (как минимум) два варианта.

Вы можете либо прописать 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.

...