Nth type in a variadic template type list

1,268 views
Skip to first unread message

sean.mid...@gmail.com

unread,
May 30, 2013, 11:05:54 PM5/30/13
to std-dis...@isocpp.org
I just finished answering a variadic templates question for a student and found a small annoyance: there is no standard way that I can find to get the Nth type out of a variadic template type.  There are a decent number of questions on the topic on SE sites and elsewhere, indicating this is not just me that ran into a use for it.

Writing the utility code is not super hard but it doesn't feel like something that should need to be written repeatedly for each project needing it.

The work-around is to construct a std::tuple using the template type list and then using std::tuple_element to get the Nth type of the tuple, which is kind of a silly work-around conceptually.  I feel it requires too much explanation to students and basically boils down to "the committee forgot a utility, here's the hack-around if you don't want to write the utility yourself."

I'd envision something like (typed from memory, probably has some errors) the following, 'type_at' name ripe for bikeshedding of course:

namespace std
{
  template <unsigned N, typename FirstType, typename ...RemainingTypes>
  struct type_at
  {
    typedef typename type_at<N - 1, RemainingTypes...>::type type;
  };

  template <typename FirstType, typename ...RemainingTypes>
  struct type_at<0, FirstType, RemainingTypes...>
  {
    typedef FirstType type;
  };
}

Usage:

  std::type_at<2, int, float, char*, std::string>::type // char*
  std::type_at<N, Types...>::type

Existing embarrassing-to-explain but easy-enough way to do things:

  std::tuple_element<N, std::tuple<Types...>>::type

Of course with the traits paper that went through recently, a variation to allow the following would be trivial to add as well:

  std::type_at_t<N, Types...>

It's super short and simple, though not such a huge gain over tuple_element aside from being more obvious and easier to teach/understand.

I suppose this could be especially useful in relaxed constexpr functions where it's possible to write a for-loop over the contents of a template list.

Compilers could implement this with intrinsics to avoid needless recursive template instantiations, of course.

Am I missing such a utility already in the library already?  If not, does this make sense to propose as an addition to the standard?

Richard Smith

unread,
May 30, 2013, 11:18:19 PM5/30/13
to std-dis...@isocpp.org
I think this is a very good idea, especially since a compiler can implement this asymptotically faster than is possible in user code. Are there any reasons to have the non-alias-template form other than consistency?

corn...@google.com

unread,
May 31, 2013, 6:21:13 AM5/31/13
to std-dis...@isocpp.org, sean.mid...@gmail.com
I think we want std::value_at<I>(args...) as well, to pick one value from a variadic function argument pack. But yes, we definitely need this.

Nikolay Ivchenkov

unread,
May 31, 2013, 6:34:16 AM5/31/13
to std-dis...@isocpp.org, sean.mid...@gmail.com
On Friday, May 31, 2013 7:05:54 AM UTC+4, sean.mid...@gmail.com wrote:
I just finished answering a variadic templates question for a student and found a small annoyance: there is no standard way that I can find to get the Nth type out of a variadic template type.  There are a decent number of questions on the topic on SE sites and elsewhere, indicating this is not just me that ran into a use for it.

Am I missing such a utility already in the library already?  If not, does this make sense to propose as an addition to the standard?

I don't like library solution, at least because it's not scalable. std::type_at is not helpful when you need to get an item of a non-type parameter pack. A core language operator ...[] could be used with any kind of parameter pack:

    template <class... Types, int... Numbers>
        void f()
    {
        using type = Types...[0];
        constexpr int number = Numbers...[0];
    }

In addition it would be nice to have special construct

    enum(Pack)

that would expand to numbers 0u, 1u, 2u, ..., sizeof...(Pack) - 1 if Pack is non-empty.

Usage:

    template <std::size_t ItemIndex, class... ItemTypes>
        typename std::tuple_element<ItemIndex, std::tuple<ItemTypes...>>::type &&
            fwd_get(std::tuple<ItemTypes...> &x)
    {
        using ret_type =
            typename std::tuple_element<ItemIndex, std::tuple<ItemTypes...>>::type &&;
        return std::forward<ret_type>(std::get<ItemIndex>(x));
    }

    template <std::size_t ItemIndex, class... ItemTypes>
        typename std::tuple_element<ItemIndex, std::tuple<ItemTypes...>>::type const &&
            fwd_get(std::tuple<ItemTypes...> const &x)
    {
        using ret_type =
            typename std::tuple_element<ItemIndex, std::tuple<ItemTypes...>>::type const &&;
        return std::forward<ret_type>(std::get<ItemIndex>(x));
    }

    template <std::size_t ItemIndex, class... ItemTypes>
        typename std::tuple_element<ItemIndex, std::tuple<ItemTypes...>>::type &&
            fwd_get(std::tuple<ItemTypes...> &&x)
    {
        using ret_type =
            typename std::tuple_element<ItemIndex, std::tuple<ItemTypes...>>::type &&;
        return std::forward<ret_type>(std::get<ItemIndex>(x));
    }

    // application of special pack expansion:
    template <class F, class... Items>
        void call_with_tuple(F &&f, std::tuple<Items...> const &x)
    {
        FORWARD(f)(fwd_get<enum(Items)>(x)...);
        // expands to: fwd_get<0>(x), fwd_get<1>(x), ..., fwd_get<sizeof...(Items) - 1>(x)
    }

Construct

    Pack...[enum(Pack)]...

could be equivalent to

    Pack...

sean.mid...@gmail.com

unread,
May 31, 2013, 2:09:41 PM5/31/13
to std-dis...@isocpp.org, sean.mid...@gmail.com
On Friday, May 31, 2013 3:34:16 AM UTC-7, Nikolay Ivchenkov wrote:
On Friday, May 31, 2013 7:05:54 AM UTC+4, sean.mid...@gmail.com wrote:
I just finished answering a variadic templates question for a student and found a small annoyance: there is no standard way that I can find to get the Nth type out of a variadic template type.  There are a decent number of questions on the topic on SE sites and elsewhere, indicating this is not just me that ran into a use for it.

Am I missing such a utility already in the library already?  If not, does this make sense to propose as an addition to the standard?

I don't like library solution, at least because it's not scalable. std::type_at is not helpful when you need to get an item of a non-type parameter pack. A core language operator ...[] could be used with any kind of parameter pack:

    template <class... Types, int... Numbers>
        void f()
    {
        using type = Types...[0];
        constexpr int number = Numbers...[0];
    }

I avoided saying anything about new language syntax out of both a desire to see something happen sooner (is the door completely closed on C++14 now?  I believe it is), and also because I'm not even remotely sure what that syntax should be (you seem to have a better idea, though).

I'm hesitant to suggest ...[] only out of consistency; it's the operator for accessing containers of homogeneous types, and typically some kind of template accessor is used elsewhere.  If we can make it so that e.g. a tuple can be accessed by [] and return the correct type for the constexpr input such that [] is just the "access element" operator for both homogeneous and heterogeneous containers then maybe it'd make more sense to me to use [].  The only way I could think to do that though would require being able to overload constexpr member functions (so we can have "only constexpr" functions) and then do something along the lines of (I believe the following syntax for constexpr overloads was previously proposed):

  template <typename ...Types>
  std::tuple {
    auto constexpr operator[](constexpr unsigned index) -> decltype(std::tuple_element<index, std::tuple<Types...>>::type)
    {
      return std::get<index>(*this);
    }
  };

Maybe someone else has a better idea on how to get a []-like syntax to work on tuples, though.

Still, short version: [] only makes sense to me if it can be consistently applied and taught as "the universal element accessor."

Nikolay Ivchenkov

unread,
Jun 1, 2013, 4:47:27 AM6/1/13
to std-dis...@isocpp.org, sean.mid...@gmail.com
On Friday, May 31, 2013 10:09:41 PM UTC+4, sean.mid...@gmail.com wrote:

I avoided saying anything about new language syntax out of both a desire to see something happen sooner (is the door completely closed on C++14 now?

I'm afraid that if this library workaround will become standard, we will have to use such a crutch forever.

I'm hesitant to suggest ...[] only out of consistency; it's the operator for accessing containers of homogeneous types, and typically some kind of template accessor is used elsewhere.

...[] and [] are different operators, as well as sizeof...() and sizeof/sizeof() are different operators (with different semantics).
 
Maybe someone else has a better idea on how to get a []-like syntax to work on tuples, though.

I would prefer to have normal core language tuples rather than wretched std::tuple. Core language tuples could have special operators, possibly non-overloadable for user-defined types:

    {}<int, double, std::string> tuple = +{1, 2.5, "text"};

    int i = tuple.[0];
    double d = tuple.[1];
    std::string s = tuple.[2];

    &{i, d, s} = +{2, 3.2, "new"};
    // instead of std::tie(i, d, s) = std::make_tuple(2, 3.2, "new");

    #define FORWARD(x) static_cast<decltype(x) &&>(x)
    #define RETURN(...) \
        noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__) \
        { return __VA_ARGS__; }

    template <class... Params>
        {}<Params &&...> forward_as_tuple(Params &&... params)
            { return &{ FORWARD(params)... }; }


    template <class F, class... Items>
        auto call_with_tuple(F &&f, {}<Items...> const &tuple)
            RETURN(FORWARD(f)(tuple->...))

    void f(int i, double d, std::string const &);

    void forwarding()
    {
        // calls f(1, 2.5, "text");
        ::call_with_tuple(f, ::forward_as_tuple(1, 2.5, "text"));
    }


Ville Voutilainen

unread,
Jun 1, 2013, 5:27:18 AM6/1/13
to std-dis...@isocpp.org
On 1 June 2013 11:47, Nikolay Ivchenkov <mk.ivc...@gmail.com> wrote:
On Friday, May 31, 2013 10:09:41 PM UTC+4, sean.mid...@gmail.com wrote:

I avoided saying anything about new language syntax out of both a desire to see something happen sooner (is the door completely closed on C++14 now?

I'm afraid that if this library workaround will become standard, we will have to use such a crutch forever.

I don't see why you're afraid of it, and what makes it a crutch.

 

I would prefer to have normal core language tuples rather than wretched std::tuple. Core language tuples could have special operators, possibly non-overloadable for user-defined types:


I think it's unlikely that we'll go to the direction of core language tuples, unless someone shows them to
be vastly superior to library tuples. Thus far I haven't seen such indication of superiority.

Nicol Bolas

unread,
Jun 1, 2013, 9:06:57 AM6/1/13
to std-dis...@isocpp.org

Some would say that it's superior due to the shorter, more readable syntax for creating and using tuples. Granted, that wouldn't be me, because shorter, more readable syntax makes people use these things far more often than they should. Just like people keep trying to shoehorn lambdas into every single functor they write, if you give people an easy way to use tuples, they will turn every problem into one that is best solved with a tuple.

I would much rather see reflection capabilities that make tuples entirely obsolete. Why make a tuple when you can query a struct's members by index? Why make a tuple if you can generate new types via template metaprogramming?

Sean Middleditch

unread,
Jun 1, 2013, 4:45:45 PM6/1/13
to std-dis...@isocpp.org
Multiple responses to the discussion below; figured that might be less
noisome than replying four separate times in a row.

On Sat, Jun 1, 2013 at 6:06 AM, Nicol Bolas <jmck...@gmail.com> wrote:
>
>
> Some would say that it's superior due to the shorter, more readable syntax for creating and using tuples. Granted, that wouldn't be me, because shorter, more readable syntax makes people use these things far more often than they should.

Encourage good coding habits by inflicting hardship? How delightfully
medieval. :)

I do agree with the idea of making it easier to do the right thing
rather than the wrong thing since most people just take the path of
least resistance whenever possible. However, I'd argue that
syntactical tuples being "too easy" in some circumstance is really
just a symptom of the more appropriate option (say, a struct with
well-named members) being too hard. For example, it might be handy if
one could define a struct while declaring a function rather than
needing to split the declarations up.

Compare the follow syntactical tuples + easy anonymous structs:

auto get_tuple() -> { int; float; }
auto get_struct() -> { int index; float value; };

with having neither:

auto get_tuple() -> std::tuple<int, float>
struct something_t { int index; float value; };
auto get_struct() -> something_t;

In the latter case, it's a lot easier to use tuples, but in the
former, there's hardly any real difference. It's not so much about
making tuples hard to use but rather making the struct version so easy
to use that - where those member identifiers are actually useful -
there's little reason to prefer the tuple version even if you're
really lazy.

The anonymous struct would make sense only in some cases of course,
but maybe it fills a gap between tuples and named structs that is
missing? My fear, mirroring probably your own, would be that novice
coders would use the anonymous struct version more often than they
should; even then, it is at least better than using the existing
std::tuple version, yes?

> I would much rather see reflection capabilities that make tuples entirely obsolete. Why make a tuple when you can query a struct's members by index? Why make a tuple if you can generate new types via template metaprogramming?

Without something like the above, the reason would be code locality.
Same reason lambdas are so popular over functors. It's much easier
both to write _and_ read/review/maintain code if you don't have to
split up closely-related data definitions and code flow into multiple
places.

Also, maybe sometimes the mandatory member identifiers of structs
would just be noise as there's no strong need or obvious choice for
them. If you're always querying struct members by index and hence
don't even need them, why be forced to try to choose any when making
the type? When meta-programming it'd be even harder to pick decent
identifiers, I think, and in many cases I'd bet you'd just be writing
code to spit out names like "member1, member2" and so on.

On Sat, Jun 1, 2013 at 1:47 AM, Nikolay Ivchenkov
<mk.ivc...@gmail.com> wrote:
>> I'm hesitant to suggest ...[] only out of consistency; it's the operator for accessing containers of homogeneous types, and typically some kind of template accessor is used elsewhere.
>
> ...[] and [] are different operators, as well as sizeof...() and sizeof/sizeof() are different operators (with different semantics).

True enough. You're right. I don't personally care for the existing
syntax examples much either but that ship has sailed.

I am a fan of aiming for reusable syntax rather than one-off special
cases, though. It's easy to think up new syntax for specific uses but
there's whole classes of cool (and now common) C++ tricks that fell
out "accidentally" from generic language designs. Meta-programming in
itself is kind of an accident originally, as I understand. Aiming for
user-overloadable operators on parameter packs with constexpr
overloads, as an example, would allow a library addition like the
following and maybe all kinds of other cool future good ideas that we
haven't even thought of yet:

template <typename ...Args>
auto operator[](Args... args, constexpr size_t index) -> typename
std::type_at<index, Args...>::type&&
{
return std::value_at<index>(args...);
}

If one is going to go the route of proposing (possibly quite complex)
core-language features, I'd aim for more generic ones. Even if they
do lead to less experienced coders doing what Nicol Bolas fears and
writing crappy code, that is going to happen anyway (inexperienced
coders even in "simple" languages like Lua or LISP write absolutely
awful code until they get a solid grasp of the proper idioms and
practices of their chosen profession/tool). The extensible solution
could be abused for evil by the inexperienced, but it could also be
used for good by the knowledgeable, or at truly worst only reasonable
useful for the one-off solution same as the one-off language addition
would be. The generic solution facilitates better libraries and many
ideas that can evolve over time rather than entrenching a single idea
forever. Unless the one-off solution is really easier to implement
than the generic one, there's no extra cost to the generic solution
but a lot of potential gain.

Hence why I'd rather provide a library solution for this particular
problem now which could then be used as building blocks for future
library additions using language additions like the example above.
Not really up to me, though, of course. :)

On Fri, May 31, 2013 at 3:21 AM, <corn...@google.com> wrote:
> I think we want std::value_at<I>(args...) as well, to pick one value from a
> variadic function argument pack. But yes, we definitely need this.

Seems sensible.

I'd be a fan of just making an overload for std::get since that
doesn't immediately look like a tuple-only facility and hence seems
"obvious." Unfortunately, I can't think of a way to resolve the
ambiguity for a type list comprised of a single std::tuple.
std::get<0>(args...) would expand to something like
std::get<0>(some_tuple) and then return the first value in the tuple
rather than the whole tuple itself as would probably be desired.

I can't think of any way to design a new std::get
overload/specialization that wouldn't have that problem; maybe someone
else can.

-

In any case, I might take a stab and writing some proposal-ese for the
template-only version of what I originally put up (plus the template
alias versions plus a version of std::value_at). If I do I'd need a
sponsor/advocate for the paper as I am not a committee member and I am
not feasibly able to make it to the vast majority of committee
meetings.

On the original-idea side of the discussion, I'm not super fond of the
name type_at, especially not the alias version which would be
type_at_t for consistency with the other aliased traits; any other
suggestions? For consistency with tuple's tuple_element the only
other thing I can think of is something like variadic_element but I'm
not really digging that at all.

-Sean

Nicol Bolas

unread,
Jun 1, 2013, 6:06:06 PM6/1/13
to std-dis...@isocpp.org, se...@middleditch.us

I don't want anonymous structs either. I want types to have typenames that are real types that can be typed in by the user. I shouldn't have to use `auto` to capture a return variable just because someone is returning some untypeable type.

In general, I've found that if I need to return more than one value, then there is semantic information associated with that grouping of information. And that semantic grouping of information deserves to have a name that is human readable and differentiable from some other `{int index; float value;}` aggregation.

Furthermore, I find that in virtually all cases, if I have an aggregate of values, I will use it in more than one place. My code is far more likely to look like this:

struct something_t { int index; float value; };
auto get_struct() -> something_t;
void set_struct(something_t);
void do_something_with_struct(const something_t &);
void other_function_that_happens_to_use_struct(std::string_view sv, int index, const something_t &);

And so forth.

Remember: we read code more than we write it. `{int index; float value:}` means nothing. `something_t` means nothing too. But `speed_for_user_t` means something; it makes it very clear what the argument means (given some context).

The anonymous struct would make sense only in some cases of course,
but maybe it fills a gap between tuples and named structs that is
missing?  My fear, mirroring probably your own, would be that novice
coders would use the anonymous struct version more often than they
should; even then, it is at least better than using the existing
std::tuple version, yes?

No. Novice programmers will balk at the wordiness of tuples; therefore, they would use them only when necessary, not when they might be slightly convenient.

Here's what your way would do. A novice would start writing code like this:


auto get_struct() -> { int index; float value; };

Then, after a while, he needs a function that takes one of those as a value. But he's too lazy and too trained to make it a real type, so he just does this:

void set_struct({ int index; float value; });

Then, he needs to add another function that takes the same collection. But now he'd have to change two functions to use the type, so he's not going to do that:

void do_something_with_struct(const { int index; float value; } &);

And he keeps going on with that over and over. We want people to make the right choice up-front. And the right choice is to give this semantic collection of data a real, meaningful typename.

> I would much rather see reflection capabilities that make tuples entirely obsolete. Why make a tuple when you can query a struct's members by index? Why make a tuple if you can generate new types via template metaprogramming?

Without something like the above, the reason would be code locality.
Same reason lambdas are so popular over functors.  It's much easier
both to write _and_ read/review/maintain code if you don't have to
split up closely-related data definitions and code flow into multiple
places.

Code locality for functors stopped being a legitimate concern once you could define local structs that could escape the functions they were defined in. That happened in C++11 too.

Furthermore, "code locality" doesn't work as a justification when it's just a generic grab bag of types, because you're trading that locality for important semantic information.

Also, maybe sometimes the mandatory member identifiers of structs
would just be noise as there's no strong need or obvious choice for
them.  If you're always querying struct members by index and hence
don't even need them, why be forced to try to choose any when making
the type?  When meta-programming it'd be even harder to pick decent
identifiers, I think, and in many cases I'd bet you'd just be writing
code to spit out names like "member1, member2" and so on.

I have very rarely had a collection of more than two items that was so generic that the items did not have some semantic concept associated with them. Granted, I generally avoid metaprogramming and spend most of my C++ time on concrete systems rather than library-type stuff.

Sean Middleditch

unread,
Jun 1, 2013, 6:42:04 PM6/1/13
to std-dis...@isocpp.org
On Sat, Jun 1, 2013 at 3:06 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> I don't want anonymous structs either. I want types to have typenames that
> are real types that can be typed in by the user. I shouldn't have to use
> `auto` to capture a return variable just because someone is returning some
> untypeable type.
>
> In general, I've found that if I need to return more than one value, then
> there is semantic information associated with that grouping of information.
> And that semantic grouping of information deserves to have a name that is
> human readable and differentiable from some other `{int index; float
> value;}` aggregation.

I' certainly see the need for well-defined types _most often_, but
I've seen the need for the anonymous collections, too. I wouldn't say
that an anonymous struct is the best thing, but compared to a tuple,
it might be better sometimes, and tuples are already here to stay. I
don't have a strong opinion on it, was just offering some ideas; I'd
have to actually play with them (and have an implementation for it) to
form a solid opinion one way or the other.

> Then, after a while, he needs a function that takes one of those as a value.
> But he's too lazy and too trained to make it a real type, so he just does
> this:
>
> void set_struct({ int index; float value; });
>
> Then, he needs to add another function that takes the same collection. But
> now he'd have to change two functions to use the type, so he's not going to
> do that:
>
> void do_something_with_struct(const { int index; float value; } &);

That's of course assuming you can declare anonymous structs as
parameters. I wouldn't propose such a thing without going so far as
full structure-based typing (a la Go/ML), but that's something that
needs a lot more thought than I'm prepared to devote to the topic
anytime soon

> Code locality for functors stopped being a legitimate concern once you could
> define local structs that could escape the functions they were defined in.
> That happened in C++11 too.

I wasn't aware of that one, actually; thought it was just a
commonly-implemented language extension. Good to know it's standard.
Thank you.

>> Also, maybe sometimes the mandatory member identifiers of structs
>> would just be noise as there's no strong need or obvious choice for
>> them. If you're always querying struct members by index and hence
>> don't even need them, why be forced to try to choose any when making
>> the type? When meta-programming it'd be even harder to pick decent
>> identifiers, I think, and in many cases I'd bet you'd just be writing
>> code to spit out names like "member1, member2" and so on.
>
>
> I have very rarely had a collection of more than two items that was so
> generic that the items did not have some semantic concept associated with
> them. Granted, I generally avoid metaprogramming and spend most of my C++
> time on concrete systems rather than library-type stuff.

I've seen a use for them over the years, but granted not a strong
enough one to make their addition particularly important. I do
actually do a lot of library-type stuff, though it's usually more in
geometric and math libraries that I've seen the use. Places that
concrete types need definitions just to pass data between two internal
routines, that sort of thing. Public interfaces need the more
well-defined semantics, of course. Really kind of devolving into an
entirely separate language feature discussion, though. :)
--
Sean Middleditch
http://seanmiddleditch.com

Tony V E

unread,
Jun 6, 2013, 1:26:12 PM6/6/13
to std-dis...@isocpp.org
I'd like to be able to do

{x, y } = func(a,b,c);

And even

void g(int x, int y);

g(untuple(func(a,b,c))); // but some better "untuple" syntax


I know std::tie() works, but I'd appreciate it as part of the language.

Tony

Johannes Schaub

unread,
Jun 7, 2013, 4:43:43 PM6/7/13
to std-dis...@isocpp.org
2013/6/1 Ville Voutilainen <ville.vo...@gmail.com>:
Core-language tuples could enable the way to have perfect forwarding
for initializer lists

template<typename T, typename ...U>
void construct(U...u);

int main() { construct<std::vector<int>>({1, 2, 3}); }

"U" will consist of a single core-language tuple type containing "int,
int, int". Based on this one could possibly construct a mechanism for
perfectly forwarding it. Such a thing is not possible at all with
library tuple implementations.

David Rodríguez Ibeas

unread,
Jun 10, 2013, 5:03:47 AM6/10/13
to std-dis...@isocpp.org
On Sat, Jun 1, 2013 at 6:06 PM, Nicol Bolas <jmck...@gmail.com> wrote:
In general, I've found that if I need to return more than one value, then there is semantic information associated with that grouping of information. And that semantic grouping of information deserves to have a name that is human readable and differentiable from some other `{int index; float value;}` aggregation.

I have very rarely had a collection of more than two items that was so generic that the items did not have some semantic concept associated with them.

Fully agree. 

Have you never heard a complaint on the interface of std::map<>? In particular, haven't you ever doubt what 'm.insert(std::make_pair(10,20)).first' means? How readable is:

if (m.insert(newelement).second) {

compared with

if (m.insert(newelement).inserted) {

When I muttered a concern to Bjarne that I felt 'auto' was going to be heavily abused and it could hinder readability as the types of the objects fall outside of the scope of the code that is being read (the exact example was from one of his slides: 'auto x = std::async(f,10);' -- what is the type of 'x'??) I was answered by different people in the group with: "Current IDEs will tell you if you hover over 'x' or 'f'". The same exact answer can be applied to the locality issue here: most editors and IDEs can make locality of code a non-issue, and at the same time a careful choice of name for the type and the members can actually make it more readable than if it is local in the first place.
Reply all
Reply to author
Forward
0 new messages