I can write a function which take a temporary array(such as {1, 2, 3}
) as an argument in two ways:
// using array
template<typename T, int N>
auto foo1(const T(&t)[N]) -> void;
// using std::initializer_list
template<typename T>
auto foo2(std::initializer_list<T> t) -> void;
Is there any guideline which tells which one is better?
They are both very different things. There is also 2 or 3 other choices that are reasonable.
template<class T, std::size_t N>
void foo_a( std::array<T, N> const& );
template<class T>
void foo_b( gsl::span<const T> );
template<class T, std::size_t N >
void foo_c( T const(&)[N] );
template<class T>
void foo_d( std::initializer_list<T> );
template<class T, class A=std::allocator<T> >
void foo_e( std::vector<T, A> const& );
template<class...Ts>
void foo_f( std::tuple<Ts...> const& );
template<class...Ts>
void foo_g( Ts const& ... );
here are 7 different ways to take a bunch of T
s.
They all have advantages and disadvantages over each other.
The closest to strictly better is foo_a
over foo_c
; foo_c
is only good in that it is more compatible with C-style arrays.
foo_b
lets you consume any of the others except foo_f
. So that is nice.
a, c and f all have compile-time determined lengths within foo
. This could make a difference, depending on what you are doing. You could in theory write a foo_b
type view that handles fixed length, but nobody bothers.
e is the only one that supports dynamic length at the call-site.
f supports non-identical types, but makes iteration a bit less clean.
All of them can be modified slightly to permit move-out (even the initializer list one with a bit more boilerplate).
d gives the easiest {}
, but g is just as clean (omit the {}
entirely).
Usually I use my home-rolled gsl::span
variant. It has an initializer_list
constructor. And I very very rarely want to deduce T
.