Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ parameter pack with single type enforced in arguments

I want to be able to do the following:

#include <array>
struct blah { };

template<typename... Args>
constexpr auto foo(Args&&... args)
{
    return std::array<blah, sizeof...(Args)>{{ args... }};
}

auto res = foo({}, {});

The following answers aren't satisfying: they just want to check that the parameter pack is of a single type, but I want to convert the values right to it in the arguments (else it does not work).

C++ parameter pack, constrained to have instances of a single type?

Parameter with non-deduced type after parameter pack

Specifying one type for all arguments passed to variadic function or variadic template function w/out using array, vector, structs, etc?

I also can't use initializer_list since I wouldn't be able to count the number of arguments to pass to the array type. And I especially don't want to type foo(blah{}, blah{});.

What are my possibilities ?

like image 318
Jean-Michaël Celerier Avatar asked Nov 24 '17 10:11

Jean-Michaël Celerier


4 Answers

A little bit expanded approach of Jarod42 for lazies (C++17):

#include <utility>
#include <array>

struct blah {};

template <class T, std::size_t I>
using typer = T;

template <class T, std::size_t N, class = std::make_index_sequence<N>>
struct bar_impl;

template <class T, std::size_t N, std::size_t... Is>
struct bar_impl<T, N, std::index_sequence<Is...>> {
    static auto foo(typer<T, Is>... ts) {
        return std::array<T, N>{{ts...}};
    }
};

template <class T = blah, std::size_t N = 10, class = std::make_index_sequence<N>>
struct bar;

template <class T, std::size_t N, std::size_t... Is>
struct bar<T, N, std::index_sequence<Is...>>: bar_impl<T, Is>... {
    using bar_impl<T, Is>::foo...;
};

int main() {
    bar<>::foo({}, {});
}

[live demo]

Edit:

Some C++14 solution which (as noted by max66) is even simpler than I expected:

#include <utility>
#include <array>

struct blah {};

template <class T, std::size_t I>
using typer = T;

template <class T = blah, std::size_t N = 10, class = std::make_index_sequence<N>>
struct bar;

template <class T, std::size_t N, std::size_t... Is>
struct bar<T, N, std::index_sequence<Is...>>: bar<T, N - 1> {
    using bar<T, N - 1>::foo;
    static auto foo(typer<T, Is>... ts) {
        return std::array<T, N>{{ts...}};
    }
};

template <class T>
struct bar<T, 0, std::index_sequence<>> {
    static auto foo() {
        return std::array<T, 0>{{}};
    }
};

int main() {
    bar<>::foo({}, {});
}

[live demo]

One more edit:

This one (as suggested by Jarod42) provides exactly the same syntax for invocation as in OP's question:

#include <utility>
#include <array>

struct blah {};

template <class T, std::size_t I>
using typer = T;

template <class T = blah, std::size_t N = 10, class = std::make_index_sequence<N>>
struct bar;

template <class T, std::size_t N, std::size_t... Is>
struct bar<T, N, std::index_sequence<Is...>>: bar<T, N - 1> {
    using bar<T, N - 1>::operator();
    auto operator()(typer<T, Is>... ts) {
        return std::array<T, N>{{ts...}};
    }
};

template <class T>
struct bar<T, 0, std::index_sequence<>> {
    auto operator()() {
        return std::array<T, 0>{{}};
    }
};

bar<> foo;

int main() {
    foo({}, {});
}

[live demo]

like image 158
W.F. Avatar answered Nov 01 '22 22:11

W.F.


Alright, if you can afford to change the syntax a little bit, this is the best I managed to find:

#include <array>

// to_array implementation taken from 
// http://en.cppreference.com/w/cpp/experimental/to_array
namespace detail {
template <class T, std::size_t N, std::size_t... I>
constexpr std::array<std::remove_cv_t<T>, N>
    to_array_impl(T (&a)[N], std::index_sequence<I...>)
{
    return { {a[I]...} };
}
}

template <class T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N])
{
    return detail::to_array_impl(a, std::make_index_sequence<N>{});
}
// End of to_array implementation

struct blah { };

template<std::size_t N>
constexpr auto foo(const blah(&arr)[N])
{
    return to_array(arr);
}

int main()
{
    auto res = foo({{}, {}});
    return 0;
}

As you can see, foo({}, {}) became foo({{}, {}}). Here is a working example: https://ideone.com/slbKi3

The issue with the way you want it (foo({}, {})) is that the compiler has no way to know what it is supposed to convert {} to.

I tried to find a way to let it know but it didn't listen at all.

like image 36
Telokis Avatar answered Nov 01 '22 22:11

Telokis


If you accept, as proposed by Telokis, to add a bracket level calling foo()

auto res = foo( { {}, {} } );

you can use the C-style array trick proposed by Telokis and a simple cycle to initialize the returned value

template <std::size_t N>
constexpr std::array<blah, N> foo (const blah(&arr)[N])
 {
   std::array<blah, N> ret;

   for ( auto i = 0U ; i < N ; ++i )
      ret[i] = arr[i];

   return ret;
 }

Unfortunately the operator[] for std::array is constexpr only starting from C++17, so the preceding foo is effectively constexpr only starting from C++17.

So you can call

auto res = foo( { {}, {} } );

also in C++11 and C++14, but

constexpr auto res = foo( { {}, {} } );

only starting from C++17.

like image 2
max66 Avatar answered Nov 01 '22 23:11

max66


One (limited) ways to keep your syntax is to have several overloads:

constexpr auto foo(const blah& a1)
{
    return std::array<blah, 1>{{ a1 }};
}

constexpr auto foo(const blah& a1, const blah& a2)
{
    return std::array<blah, 2>{{ a1, a2 }};
}

// ...

// Up to N
constexpr auto foo(const blah& a1, const blah& a2, .., const blah& aN)
{
    return std::array<blah, N>{{ a1, a2, .., aN }};
}

W.F. in his answer shows a way to generate it thanks to variadic at class scope.

like image 2
Jarod42 Avatar answered Nov 02 '22 00:11

Jarod42