7

Using a template struct such as many below, it's possible to return a fixed set of possibly unmovable objects, and receive them using the c++17 structured binding (auto [a, b, c] = f(); declares the variables a, b and c and assigns their value from f returning for example a struct or tuple).

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();
}

As explained in these two questions and answers (Do std::tuple and std::pair support aggregate initialization? and especially the accepted answer by ecatmur, also Multiple return values (structured bindings) with unmovable types and guaranteed RVO in C++17), std::tuple does not support aggregate initialization. That means that it can not be used to hold and return unmovable types. But a simple struct like many can do this, which leads to the question:

Is it possible to create a variadic version of many which works with any number of arguments?

Update: in a templated version of many, will the following guide syntax be allowed?

template<typename Args...>    
many(Args...) -> many<Args...>;
CoffeeTableEspresso
  • 2,614
  • 1
  • 12
  • 30
Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • Note that there's a proposal somewhere for sg like `auto std::tie(a, b, c) = ...`. – lorro Jul 15 '16 at 10:40
  • @lorro IMO it is a bad idea: it makes core language feature pose as library feature. Not to mention that compiler will have to solve potential `using`, namespace aliaces and other things. – Revolver_Ocelot Jul 15 '16 at 11:06
  • @Revolver_Ocelot: I also think it could be done better, but for some different / additional reasons: namely, it's not orthogonal to other features. My favourite way to do this would be to allow declaring variables *everywhere*, including in position of function parameters. The other issue I have is then you'll write `a=...`, whereas what you mean is to call the *constructor* of `decltype(a)`, That are two very different things. – lorro Jul 15 '16 at 11:35
  • 2
    @lorro : That feature is already approved and in the latest C++17 draft (N4606), but the syntax is `auto [a, b, c] = ...;`. – ildjarn Jul 16 '16 at 02:33
  • @ildjan Yes that's a prerequisite for this question :-) and used in the question. – Johan Lundberg Jul 16 '16 at 06:36
  • @lorro, Perhaps you missed that as ildjarn said it's already voted in, but not with the syntax you wrote, but the syntax I already use in my question, and more in the related questions. Or do you really mean that there is a notion of adding *also* the possibility to use that syntax? – Johan Lundberg Jul 16 '16 at 09:25
  • @ildjarn and Johan Lundberg: Thanks and thanks for the comments, I agree and sorry for missing this. – lorro Jul 16 '16 at 11:41

2 Answers2

6

In C++17 aggregate initialization will be able to initialize public base classes. So you can use inheritance + pack expansion to build such class. To make it work with structured bindings, you will have to expose tuple interface: specialize std::tuple_size and std::tuple_element and provide get function for your class:

//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);
}

Demo (right now works only in clang 3.9): http://melpon.org/wandbox/permlink/9NBqkcbOuURFvypt

Notes:

  • In demo there is a commented out implementation of nth_type, you can use to inplement tuple_element directly and not defer it to tuple_element<tuple> implementation.
  • get<many> should be either a member function of many or be placed in associated namespace for structured bindings to work. You should not overload std::get (It would be UB anyway).
  • I left implementation of get for non-const references and r-value references as excercise for the reader.
  • There is no example of using structured bindings and guides, because clang does not support them (in fact I do not know any compiler which supports them). In theory
    template<typename... Types> many(Types...) -> many<Types...>; should work.
Revolver_Ocelot
  • 8,609
  • 3
  • 30
  • 48
  • Good, but the question was also how to get this working with deduced template arguments as with my 3-member example. Something like `template many(Args...) -> many;` ? – Johan Lundberg Jul 15 '16 at 11:16
  • @JohanLundberg I suppose, if your original code is valid, you will just have to change your "guide" to use variadic template arguments. But I couldn't find any online compiler which would accept your original code, so I couldn't test. – Revolver_Ocelot Jul 15 '16 at 11:21
  • 1
    @Revolver_Ocelot: To make this work, you'd have to give `many` the `tuple_element` and other interfaces to allow structured binding to work. Because structured binding can't get at base classes. Also, you can't use the same base class twice, so you can't have the same type in `Types`. – Nicol Bolas Jul 15 '16 at 13:15
  • 1
    @NicolBolas Yes. I was wrong to assume that structured binding follows the rules of "backward aggregate initialization". For duplicate types, I will probably add dummy size_t parameter to the holder structure and enumerate all base classes with index sequence, forcing each type to be unique (AFAIK tuple implementation often do exactly that.). Might update my answer in an hour or so. – Revolver_Ocelot Jul 15 '16 at 13:35
2

There was a discussion about this on std-proposals just the other day.

We don't have final wording yet, or for that matter a compiler (that I'm aware of) that supports deduction guides, but according to Richard Smith the following deduction guide should work (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>

So yes, a variadic deduction guide for an aggregate should also work and will work with aggregate initialization syntax. It won't work without a deduction guide, as without a deduction guide the compiler needs a constructor.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 2
    The problem is that you can't have a type who's members are variadic. That is, you can't do something like `Types varname...;` – Nicol Bolas Jul 15 '16 at 13:16