Как я могу поэлементно назначить кортеж, используя выражения сгиба?

У меня есть тип, эффективно обертывающий вариативныйstd::tupleтак:

      #include <iostream>
#include <tuple>

template <typename ...Args>
struct Foo {
    std::tuple<Args...> t;

    Foo(Args&&... a)
        : t{ std::forward<Args>(a)... }
    { }

    Foo& operator +=(const Foo& f) {
        std::apply([&](auto&&... ts) {
            std::apply([...ts = std::forward<decltype(ts)>(ts)](auto&&... fts) {
                ((ts += fts), ...);
            }, f.t);
        }, t);
        return *this;
    }

    friend std::ostream& operator <<(std::ostream& os, const Foo& f) {
        std::apply([&](auto&&... ts) {
            ((os << ts << ' '), ...);
        }, f.t);
        return os;
    }
};

int main() {
    Foo goy{ 1, 2, 3 };
    Foo bar{ 4, 5, 6 };
    goy += bar;
    std::cout << goy << std::endl; // expect 5 7 9 as output
}

Теперь я хочу иметь возможность добавлять-назначать экземпляры этого типа, добавляя записи кортежа поэлементно, как указано в фрагменте кода и желательно с использованием fold-выражений вместо довольно неуклюжих конструкций с использованиемstd::index_sequence<>и так. Однако моя попытка терпит неудачу на уровне компилятора, с лязгом, внутренним сбоем компилятора и диагностикой GCC:

       error: assignment of read-only variable 'ts#0'
   15 |                 ((ts += fts), ...);
      |                  ~~~~^~~~~~~

См. Богболт

Я не понимаю, почемуtsздесь неизменно. Так что же не так? Является ли программа неправильной и/или глючат ли компиляторы? Есть ли элегантное решение?

2 ответа

лямбда-захватconstпо умолчанию вам нужно добавить mutable, чтобы изменить его.

      std::apply([...ts = std::forward<decltype(ts)>(ts)](auto&&... fts) mutable { ... }

хотя я не понимаю, почему вы фиксируете это здесь по значению, мне кажется, что вы действительно хотите захватить их по ссылке.

      std::apply([&](auto&&... fts){ ... }

Вы можете реализовать это, используяstd::index_sequenceи используя вспомогательную функцию. Имхо код, который вы получаете, легче понять, чем использовать вложенныйstd::applyс:

      template <typename ...Args>
struct Foo
{
    ...

    Foo& operator +=(const Foo& f) {
        AddHelper(f, std::make_index_sequence<sizeof...(Args)>{});
        return *this;
    }

    ...
private:
    template<size_t ... I>
    void AddHelper(Foo const& other, std::index_sequence<I...>)
    {
        ((std::get<I>(t) += std::get<I>(other.t)), ...);
    }
};
Другие вопросы по тегам