Возвращаемые переменные агрегаты (struct) и синтаксис для С++ 17 Variadic template "Руководство по построению выводов"

Используя структуру шаблона, такую ​​как many ниже, можно вернуть фиксированный набор, возможно, неотъемлемых объектов, и получить их с помощью структурированного связывания С++ 17 (auto [a,b,c] = f(); объявляет переменные ab и c и назначает их значение от f, возвращающее, например, структуру или кортеж).

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};

// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;

auto f(){ return many{string(),5.7, unmovable()}; }; 

int main(){
   auto [x,y,z] = f();
}

Как объясняется в этих двух вопросах и ответах (Выполняет инициализацию агрегации std:: tuple и std:: pair?  и особенно принятый ответ ecatmur, также Множественные возвращаемые значения (структурированные привязки) с неизменяемыми типами и гарантированное RVO в С++ 17), std::tuple не поддерживает агрегатную инициализацию. Это означает, что он не может использоваться для хранения и возврата неизменяемых типов. Но простая структура типа many может сделать это, что приводит к вопросу:

Можно ли создать вариационную версию many, которая работает с любым количеством аргументов?

Обновление: в шаблонной версии many разрешен ли следующий синтаксис?

template<typename Args...>    
many(Args...) -> many<Args...>;

Ответ 1

В С++ 17 агрегатная инициализация сможет инициализировать общедоступные базовые классы. Таким образом, вы можете использовать наследование + расширение пакета для создания такого класса. Чтобы заставить его работать со структурированными привязками, вам нужно будет открыть интерфейс кортежа: specialize std::tuple_size и std::tuple_element и предоставить get функцию для вашего класса:

//Headers used by "many" class implementation
#include <utility>
#include <tuple>

namespace rw {
    namespace detail {

    template <size_t index, typename T>
    struct many_holder
    { T value; };

    template <typename idx_seq, typename... Types>
    struct many_impl;

    template <size_t... Indices, typename... Types>
    struct many_impl<std::index_sequence<Indices...>, Types...>: many_holder<Indices, Types>...
    {};

    }

template <typename... Types>
struct many: detail::many_impl<typename std::make_index_sequence<sizeof...(Types)>, Types...>
{};

template<size_t N, typename... Types> 
auto get(const rw::many<Types...>& data) -> const std::tuple_element_t<N, rw::many<Types...>>&
{
    const rw::detail::many_holder<N, std::tuple_element_t<N, rw::many<Types...>>>& holder = data;
    return holder.value;
}

}

namespace std {
    template <typename... Types>
    struct tuple_size<rw::many<Types...>> : std::integral_constant<std::size_t, sizeof...(Types)> 
    {};

    template< std::size_t N, class... Types >
    struct tuple_element<N, rw::many<Types...> >
    { using type = typename tuple_element<N, std::tuple<Types...>>::type; };
}

//Headers used for testing
#include <iostream>
#include <string>

int main()
{
    rw::many<int, std::string, int> x = {42, "Hello", 11};
    std::cout << std::tuple_size<decltype(x)>() << '\n' << rw::get<1>(x);
}

Демо (сейчас работает только в clang 3.9): http://melpon.org/wandbox/permlink/9NBqkcbOuURFvypt

Примечания:

  • В демонстрации есть прокомментированная реализация nth_type, которую вы можете использовать для реализации tuple_element напрямую и не откладывать ее на реализацию tuple_element<tuple>.
  • get<many> должна быть либо функцией-членом many, либо помещаться в соответствующее пространство имен для структурированных привязок для работы. Вы не должны перегружать std::get (в любом случае это будет UB).
  • Я оставил реализацию get для ссылок не const и ссылок на r-значение в качестве упражнения для читателя.
  • Нет примера использования структурированных привязок и руководств, потому что clang их не поддерживает (на самом деле я не знаю компилятора, который их поддерживает). Теоретически template<typename... Types> many(Types...) -> many<Types...>; должен работать.

Ответ 2

На прошлой неделе в std-предложениях было обсуждение об этом.

У нас еще нет окончательной формулировки, или, если на то пошло, компилятор (что я знаю), который поддерживает руководства по вычитанию, но, по словам Ричарда Смита, следующее руководство по вычету должно работать (precis'd):

template<class A, class B>
struct Agg
{
    A a;
    B b;
};

template<class A, class B>
Agg(A a, B b) -> Agg<A, B>;

Agg agg{1, 2.0}; // deduced to Agg<int, double>

Итак, да, руководство по вариационной дедукции для совокупности также должно работать и будет работать с синтаксисом агрегатной инициализации. Он не будет работать без руководства по вычитанию, так как без руководства для вычитания компилятору нужен конструктор.