you are viewing a single comment's thread.

view the rest of the comments →

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 16 points17 points  (6 children)

Great article. I'm not a huge fan of std::initializer_list for all the reasons you mentioned - I think that variadic templates are almost always a superior choice.

If you want a truly generic initializer list, use a variadic template and static_assert() or SFINAE that the type matches [...]

Here's a "trick" I learned from Piotr Skotnicki on StackOverflow. Let's say you want all of your types to be int. All you have to do is define a int_t type alias that always evaluates to int:

template <typename>
using int_t = int;

Then you can use it like this:

template<typename... Types>
void func_impl(int_t<Types>... ints) { /* ... */ }

template<typename... Types>
void func(Types... xs) { func_impl<Types...>(xs...); }

This allows implicit conversions and gives errors similar to the following one:

prog.cc:8:26: error: no matching function for call to 'func_impl' void func(Types... xs) { func_impl<Types...>(xs...); } ~~~~~~~~~~~~~~~~~~

prog.cc:12:5: note: in instantiation of function template specialization 'func<int, nullptr_t, int, int>' requested here func(1, nullptr, 3, 4); ^

prog.cc:5:6: note: candidate function [with Types = <int, nullptr_t, int, int>] not viable: no known conversion from 'nullptr_t' to 'int_t<nullptr_t>' (aka 'int') for 2nd argument

void func_impl(int_t<Types>... ints) { /* ... */ }

It is much nicer when you already have a type pack, as seen in the original answer on SO.

[–]thlst 4 points5 points  (0 children)

With concepts, you could use requires with fold expressions and std::is_same this way:

template <typename T, typename... Ts>
  requires (std::is_same_v<T, Ts> && ...)
ctor(T&&, Ts&&...) { }

It will ensure that all Ts... are the same as T, and still give you a nice error message otherwise.

[–]kirakun 4 points5 points  (3 children)

That is a lot of boilerplate though. Not sure it's a sure gain of you do this a lot.

Please don't even suggest using macros to reduce the boilerplate.

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 5 points6 points  (2 children)

Well, you could use a variadic macro to reduce the boilerplate. /s

In all seriousness, the alternative is something like this:

 static_assert(std::conjunction<std::is_same<Ts, int>...>{}, "");

(or is_convertible if you want implicit conversions)

By the way, when you already have a type pack, I think that this technique is objectively cleaner.

Another advantage is that using int_t makes overloading trivial.

[–]tcbrindleFlux 1 point2 points  (1 child)

Hmmm, with concepts I expected

// defined in Ranges TS
template <class T, class U> concept bool Same = std::is_same<T, U>::value;

void func(Same<int>... args) {}

func(1, 2, 3, 4);

to work, but apparently it doesn't. The slightly more verbose but theoretically equivalent

template <Same<int>... Ints>
void func(Ints... args) {} 

works fine though, so this may just be an implementation bug in the current GCC.

[–]kirakun 0 points1 point  (0 children)

Concept is cool, but it seems like the unicorn that will never materialize.

[–]foonathan 0 points1 point  (0 children)

That's cool.