9

I'm trying to create a function that can take multiple parameters of the same type, passed in as a template. The number of arguments is known in compile time:

struct Foo
{
    int a, b, c;
};

template <uint32_t argsCount, typename T>
void fun(T ...args) // max number of args == argsCount
{
    // ...
    // std::array<T, argsCount>{ args... };
}

int main()
{
    fun<3, Foo>( { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } );

    // Dont want to do:
    // fun( Foo{}, Foo{}, Foo{} );
    // nor:
    // fun<Foo, Foo, Foo>( ... );

    return 0;
}

I must take into consideration these constraints:

  • no heap memory allocations
  • no va_args

Is it possible to do something similar in C++14 (preferably C++14, but curious what are the solutions in newer versions)?

edit: cleaned up the initial sloppy pseudcode.

wjan
  • 93
  • 1
  • 5
  • 1
    Note that `typename T` should be `typename... T` – Jason Jun 16 '22 at 14:19
  • Something == Foo? – Jarod42 Jun 16 '22 at 14:21
  • Is there a specific reason you're excluding `fun( Someting{}, Something{}, Something{} )`? That seems like a rather elegant call, and it takes advantage of template deduction. – Stephen Newell Jun 16 '22 at 14:22
  • `fun( std::array{{ 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }} );`? – Jarod42 Jun 16 '22 at 14:23
  • 1
    @AnoopRana All linked dupes are irrelevant to this question - OP wants to specify the type `Foo` only *once*. I don't think it's possible with variadic templates. – Evg Jun 16 '22 at 14:45
  • @Evg The same was done in the linked dupes. For once, read the OP's question carefully *"I'm trying to create a function that can take multiple parameters of the same type,"*. – Jason Jun 16 '22 at 14:57
  • @AnoopRana I've read it, and you didn't. Just show me how a variadic template implementation could work with specifying a type only once. `class... T` is irrelevant here, because those `T...` can't be deduced from braced lists `{...}`. – Evg Jun 16 '22 at 15:01
  • Some dupes: [dupe1](https://stackoverflow.com/questions/3703658/specifying-one-type-for-all-arguments-passed-to-variadic-function-or-variadic-te), [dupe2](https://stackoverflow.com/questions/38528801/c-parameter-pack-constrained-to-have-instances-of-a-single-type), [dupe3](https://stackoverflow.com/questions/71733467/c-multiple-function-parameters-with-varargs-of-a-specific-type), [dupe4](https://stackoverflow.com/questions/71820825/c-function-with-a-viariable-number-of-arguments-of-a-certain-type) – Jason Jun 16 '22 at 15:01
  • @Evg If tomorrow someone ask a new question with the same description as this one except that in that new question they write *"by specifying two types"* then that doesn't make it a completely new question. The concept is the same in both cases. If one understands the former they'll be able to understand the latter. – Jason Jun 16 '22 at 15:05
  • @AnoopRana OP explicitly stated what they *don't want to do* in the question. That requirement is an essential part of this question. All those dupes don't satisfy this requirement. Have are they dupes then? – Evg Jun 16 '22 at 15:07
  • 1
    From all the dups, only 2 of their answers matches OP needed (similar answer than Yakk - Adam Nevraumont's one). – Jarod42 Jun 16 '22 at 15:21
  • 1
    Why don't you accept an std::array instead of varadic arguments? – Cedric Martens Jun 16 '22 at 22:34

3 Answers3

9

With a slightly different syntax on the call site, you could do it by taking a reference to an array:

template<typename T, std::size_t N>
void fun(const T(& args)[N]) {
    auto args_as_stdarray = std::to_array(args);
}

fun<Foo>({{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
Evg
  • 25,259
  • 5
  • 41
  • 83
8

If you change the function into a functor, you can introduce a parameter pack in the body of the type of the functor.

First create a helper to turn <N, T> -> std::tuple<T, T, T, ..., T> (N times):

template<std::size_t N, typename T>
struct repeat {
private:
    // enable_if<true, T> == type_identity<T>
    template<std::size_t... I>
    static std::enable_if<true, std::tuple<std::enable_if_t<I==I, T>...>>
    get(std::index_sequence<I...>);
public:
    using type = typename decltype(get(std::make_index_sequence<N>{}))::type;
};

Then have your functor take a std::tuple<Args...>, where Args will be a parameter pack with T N times:

template<typename T, typename Args>
struct fun_t;

template<typename T, typename... Args>
struct fun_t<T, std::tuple<Args...>> {
    static constexpr std::size_t argsCount = sizeof...(Args);

    void operator()(Args... args) const {
        // ...
        // std::array<T, argsCount>{ args... };
    }
};

template<std::uint32_t argCount, typename T>
constexpr fun_t<T, typename repeat<argCount, T>::type> fun;


int main() {
    fun<3, Foo>({ 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 });
}
Artyer
  • 31,034
  • 3
  • 47
  • 75
7
template <class T0, class...Ts,
  std::enable_if_t< std::is_same_v<T0, Ts> && ..., bool > =true
>
void fun(T0 arg0, Ts ...args)
{
  constexpr auto argsCount = sizeof...(args)+1;
  std::array<T0, argsCount>{ arg0, args... };
}

this is ; it could be done in , it would just require more typing and I am lazy (replacing the fold expression mainly).

This requires the call site have Foos, which you don't want.

template<std::size_t I, class T>
struct helper { using type = T; };
template<std::size_t I, class T>
using X = typename helper<I,T>::type;

template<class T, std::size_t...Is>
void fun(std::index_sequence<Is...>, X<Is, T>... ts) {
  std::array<T, sizeof...(Is)> args(ts...);
}

use:

fun<Foo>( std::make_index_sequence<7>, /* 7 Foo objects */ );

we can also do:

fun<Foo, 7>()( /* 7 Foo objects */ );

with a slight change:

template<class T, std::size_t N> 
auto fun() {
  auto seq = std::make_index_sequence<N>{};
  return []<std::size_t...Is>(std::index_sequence<Is...>) {
    return [](X<Is, Foo>... ts) {
      std::array<Foo, sizeof...(Is)> args{ts...};
    };
  }( seq );
}

this is , but you can backport to by writing argument pack unpackers.

The trick here is that foo<Foo,7>() returns a function object that knows it takes 7 Foo objects exactly.

Live example.

In , we can:

template<class T, class Pack>
struct fun_impl;
template<class T, std::size_t...Is>
struct fun_impl<T, std::index_sequence<Is...>> {
  void operator()(X<Is, T>... ts)const {
    std::array<T, sizeof...(Is)> args{ts...};
  }
};
template<class T, std::size_t N>
auto fun() {
  return fun_impl<T, std::make_index_sequence<N>>{};
}

Live example (based off @Jarod42's version below).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524