Почему необработанный фигурный конструктор {} не возвращает значение? - PullRequest
0 голосов
/ 28 августа 2018

Допустим, у вас есть класс с переменной std::tuple, который можно перемещать с помощью аргументов + 1 новый аргумент. При построении с использованием std::apply() и необработанного конструктора фигурных скобок этот конструктор не возвращает значение r. Что означает, что класс не создан. Ниже приведен пример для уточнения.

#include <cstdio>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>

template <class... Args>
struct icecream {
    icecream() = default;

    template <class... MoreArgs>
    icecream(icecream<MoreArgs...>&& ice) {
        std::apply(
                [this](auto&&... ds) {
                    data = { std::move(ds)..., {} };
                },
                std::move(ice.data));
    }

    // This works :

    // template <class... MoreArgs>
    // icecream(icecream<MoreArgs...>&& ice) {
    //  std::apply(
    //          [this](auto&&... ds) {
    //              data = { std::move(ds)...,
    //                  std::move(std::vector<double>{}) };
    //          },
    //          std::move(ice.data));
    // }

    std::tuple<std::vector<Args>...> data{};
};

int main(int, char**) {
    icecream<int> miam;
    std::get<0>(miam.data).push_back(1);
    std::get<0>(miam.data).push_back(2);

    icecream<int, double> cherry_garcia{ std::move(miam) };

    printf("miam : \n");
    for (const auto& x : std::get<0>(miam.data)) {
        printf("%d\n", x);
    }

    printf("\ncherry_garcia : \n");
    for (const auto& x : std::get<0>(cherry_garcia.data)) {
        printf("%d\n", x);
    }

    return 0;
}

Вывод:

miam : 
1
2

cherry_garcia : 
1
2

Пример немного тупой, но иллюстрирует суть. В первом конструкторе перемещения используется {} и создается конструкция копирования кортежа. Если вы раскомментируете второй конструктор с жестко закодированным std::move(), тогда он будет работать.

Я тестирую на последней версии VS, последней версии clang и последней версии gcc. Все имеют одинаковый результат. (wandbox: https://wandbox.org/permlink/IQqqlLcmeyOzsJHC)

Итак, вопрос в том, почему бы не вернуть значение? Я явно что-то упустил в фигурном конструкторе. Возможно, это не имеет ничего общего с разнообразными вещами, но я подумал, что могу показать реальный сценарий.

1 Ответ

0 голосов
/ 28 августа 2018

Почему необработанный фигурный конструктор {} не возвращает значение?

Проблема в другом.

Проблема в том, что

data = { std::move(ds)..., {} };

вызовите "прямой конструктор" (конструктор (2) в этой странице ),

constexpr tuple( const Types&... args );       (2)

не «конвертирующий конструктор» (constructor (3))

template< class... UTypes >
constexpr tuple( UTypes&&... args );           (3)

что вы ожидаете.

Проблема в том, что "{}" недостаточно для компилятора, чтобы вывести тип (последний тип для списка UTypes... в конструкторе (3)), так что конструктор (3) исключается и Компилятор выбирает конструктор (2).

Конструктор Whit (2), "{}" допустим для создания объекта последнего типа Types... списка, потому что Types... известен и не должен быть выведен.

Но constructor (2) - это конструктор копирования (с точки зрения Types... кортежа), а не прямой конструктор как конструктор (3), поэтому первый вектор копируется, а не перемещается.

При звонке

все по-другому
data = { std::move(ds)..., std::move(std::vector<double>{}) };

или также

data = { std::move(ds)..., std::vector<double>{} };

, поскольку последний аргумент может быть явно выведен как std::vector<double>{} &&, поэтому компилятор вызывает «конструктор преобразования» (constructor (3)) и перемещает содержимое первого вектора.

Не по теме: вместо использования std::vector<double>{}, который работает только тогда, когда double является последним из типов в Args..., я предлагаю написать более общий код, используя std::tuple_element.

Более того, я предлагаю SFINAE включить ваш конструктор только тогда, когда sizeof...(MoreArgs)+1u == sizeof...(Args).

Возможно также std::forward() (включение идеальной пересылки) вместо std::move() внутри лямбды.

Поэтому я предлагаю следующий конструктор

template <typename ... MoreArgs,
   std::enable_if_t<sizeof...(MoreArgs)+1u == sizeof...(Args)> * = nullptr>
icecream(icecream<MoreArgs...>&& ice) {
    std::apply(
            [this](auto && ... ds) {
                data = { std::forward<decltype(ds)>(ds)..., 
                         std::tuple_element_t<sizeof...(Args)-1u,
                                              decltype(data)>{} };
            },
            std::move(ice.data));
}
...