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 Ts.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With