55

This question is about guaranteeing all arguments are of the same type while exhibiting a reject-early behavior with a clean compiler error, not a template-gibberish error

I'm creating a function (possibly member function, not that it matters... maybe it does?) that needs to accept an unknown number of arguments, but I want all of them to be the same type. I know I could pass in an array or vector, but I want to be able to accept the list of args directly without extra structure or even extra brackets. It doesn't look like variadic functions by themselves are typesafe, and I wasn't sure how to go about this w/ variadic template functions. Here's essentially what I'm aiming for (more than likely not correct code, and totally not for the purpose of getting lists of dragons, lol):

//typedef for dragon_list_t up here somewhere.

enum Maiden {
    Eunice
    , Beatrice
    , Una_Brow
    , Helga
    , Aida
};

dragon_list_t make_dragon_list(Maiden...) {
    //here be dragons
}

OR

template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
    //here be dragons
}

USAGE

dragon_list_t dragons_to_slay
    = make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;

Tried a few things similar to the above already, no dice. Suggestions? Obvious oversights I may have made? I know it may not be a huge deal to do this instead:

dragon_list_t make_dragon_list(std::array<Maiden> maidens) {
    //here be dragons.
}
dragon_list_t dragons_to_slay
    = make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida})
;

but I'd much rather be able to do it the first way if possible.

Brett Rossier
  • 3,420
  • 3
  • 27
  • 36
  • For a `concept` based solution (C++20) see answer for: [Is there a way to define a variadic number of arguments of the same type?](https://stackoverflow.com/questions/55716940/is-there-a-way-to-define-a-variadic-number-of-arguments-of-the-same-type/61483494#61483494) – Amir Kirsh Apr 28 '20 at 15:23
  • Does this answer your question? [Variable number of parameters in function in C++](https://stackoverflow.com/questions/1579719/variable-number-of-parameters-in-function-in-c) – xskxzr Aug 04 '21 at 03:29
  • @xskxzr No, it doesn't. Read my comment on the accepted answer. – Brett Rossier Aug 05 '21 at 13:22

15 Answers15

39

You can just accept the arguments by the variadic template and let typechecking check the validity later on when they are converted.

You can check convertibility on the function interface level though, to make use of overload resolution for rejecting outright wrong arguments for example, by using SFINAE

template<typename R, typename...> struct fst { typedef R type; };

template<typename ...Args>
typename fst<void, 
  typename enable_if<
    is_convertible<Args, ToType>::value
  >::type...
>::type 
f(Args...);

For your use-case if you know the steps to go from an std::array<> to your dragon_list_t then you have already solved it though according to the first option above ("convert-later"):

template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
    std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
    // here be dragons
}

If you combine this with the above is_convertible approach you have a reject-early template that also does overload resolution on arguments and rejects them if not applicable.

Motti
  • 110,860
  • 49
  • 189
  • 262
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 2
    +1 for giving OP exactly what he asked for, although it's for better or worse – Potatoswatter Sep 13 '10 at 19:49
  • I haven't played around with the variadic templates enough to know if the early convertible is called for, but +1 the std::array conversion, didn't think of that. – Todd Gardner Sep 13 '10 at 19:57
  • 1
    Hmm, I think the reject-early template isn't that good for general case. If Dragon was a base class, then your first argument (or all your arguments), may be a derived class, and therefor not convertible to each other. When dragon is an enum, it could be the same thing if your first class is convertible to integer type but not an integer type itself. I think relying on overloading is more extensible. – Todd Gardner Sep 13 '10 at 20:12
  • 2
    @Todd the reject-early template checks only convertibility of the arguments to `ToType` (`Dragon`), not of the arguments to each other. So D1->B is checked and D2->B too, but D1->D2 is not checked. – Johannes Schaub - litb Sep 13 '10 at 20:13
  • Out of curiosity, if the convert-later option were implemented in a library that another dev was using, what kind of error would they see if they didn't have the implementation for make_dragon_list, just a header, and they fed it the wrong types? Would that not even be a valid concern, or would it be a reason to favor the reject-early option? – Brett Rossier Sep 14 '10 at 13:24
  • @pheadbaq i have no idea. It really depends on the very code within the template. But it won't be nice i think – Johannes Schaub - litb Sep 14 '10 at 18:52
24

If you don't use template on the parameter not in the pack, the variadic function will resolve to have all arguments of the same type.

Here's an example for an extended max function that only accepts ints (or types convertible to int).

int maximum(int n) // last argument must be an `int`
{
    return n;
}

template<typename... Args>
int maximum(int n, Args... args) // first argument must be an int
{
    return std::max(n, maximum(args...));
}

Explanation: When you unpack the argument pack (args...) the compiler looks for the best overload. If the pack had only one parameter then the best candidate is maximum(int) so the only parameter must be and of type int (or convertible to int). If there are more than one elements in the pack then the only candidate is maximum(int, typename...) so the first argument must be of type int (or convertible to int). It's simple to prove by induction that all the types in the pack must be of a type convertible to int.

Motti
  • 110,860
  • 49
  • 189
  • 262
  • Interesting... can't say I understand why this works, any info/links on that? – Brett Rossier Sep 14 '10 at 04:44
  • 4
    @BrettRossier asked a long time ago, but for anyone wondering an example may help: `maximum(A, B, C)` is evaluated as `std::max(A, maximum(B, C))`, in turn evaluated as `std::max(A, std::max(B, maximum(C)))`: during the expansion first A and later B (and in general all but the last value) end up being the first argument to distinct calls to the variadic form of `maximum`, so they're passed to the `int n` argument, whereas C ends up passed to `maximum(int n)`. So, all arguments must be `int`s. – Tony Delroy Apr 16 '21 at 19:06
12

Since you've included the C++0x tag, the obvious answer would be to look up initializer lists. An initializer list lets you specify a number of arguments to a ctor that will be automatically converted to a single data structure for processing by the ctor.

Their primary (exclusive?) use is for exactly the sort of situation you've mentioned, passing a number of arguments of the same type to use in creating some sort of list/array/other collection of objects. It'll be supported by (for one example) std::vector, so you could use something like:

std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};

to create a vector of three dragon objects. Most (all?) of the other collections will include the same, so if you really insist on a list of dragons you should be able to get that pretty easily as well.

Jens
  • 8,423
  • 9
  • 58
  • 78
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Didn't even think to look at init lists, thanks for the suggestion. Still chewing on all these answers :\ – Brett Rossier Sep 13 '10 at 20:35
  • Move-only types cannot be passed via std::initializer_list, at least, not without additional hacks. It also requires an extra set of brackets, for the initializer_list. It would be nice to be able to specify a variadic template with parameter of the same type – Nuclear Oct 29 '21 at 11:58
11

A recent proposal, Homogeneous variadic functions, addresses this by making something like your first construct legal. Except of course to use the parameter pack you will have to name it. Also the exact syntax doesn't seem very concrete yet.

So, under the proposal this will actually be legal (you can see a similar construct in the paragraph "The template introducer" in the paper):

dragon_list_t make_dragon_list(Maiden... maidens) {
    //here be dragons
}
Jan Hošek
  • 187
  • 1
  • 9
  • 7
    can't understand why this doesn't already exist in the language! i want this all of the time. – Gukki5 Nov 10 '19 at 20:27
  • 1
    Rejected: [https://github.com/cplusplus/papers/issues/297](https://github.com/cplusplus/papers/issues/297) – Mike Kaganski Jan 26 '21 at 09:37
  • The github issue only lists the final vote numbers, no rationales; but maybe because of issues like possible ambiguity with C-style variadic functions? E.g., you can't declare such a homogenous variadic function without the pack name, as common in forward declarations... – Mike Kaganski Jan 26 '21 at 10:18
7

Although the question is tagged C++11, I think a C++20 + concepts solution would be worth adding seeing as though there is now support in GCC and others will soon follow.

first define a simple concept

class mytype{};

template<typename T>
concept MyType = std::is_same<T, mytype>::value;

then simply use variadic template parameters

template<MyType ... Args>
void func(Args &&... args){
    // do something here
}

Much easier with the advent of concepts!

RamblingMad
  • 5,332
  • 2
  • 24
  • 48
  • Concepts that made it into C++2a (so far) don't include this. [See here](https://botondballo.wordpress.com/2017/08/02/trip-report-c-standards-meeting-in-toronto-july-2017/#concepts). – Noein Sep 09 '17 at 15:23
  • @Noein sorry for the *late* reply: https://godbolt.org/z/saxMjcPPh – RamblingMad Dec 27 '22 at 06:42
5

I recently needed to constrain a parameter pack to be only one type, or at least convertible to that type. I ended up finding another way:

#include <type_traits>
#include <string>

template <template<typename> class Trait, typename Head, typename ...Tail> 
struct check_all {
  enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value };
};

template <template<typename> class Trait, typename Head>
struct check_all<Trait, Head> {
  enum { value = Trait<Head>::value };
};

template <typename ...Args> 
struct foo {
  // Using C++11 template alias as compile time std::bind
  template <typename T>
  using Requirement = std::is_convertible<double, T>;
  static_assert(check_all<Requirement, Args...>::value, "Must convert to double");
};

int main() {
  foo<int, char, float, double>();
  foo<int, std::string>(); // Errors, no conversion
}

The thing that I liked about this solution is that I can apply check_all to other traits too.

Flexo
  • 87,323
  • 22
  • 191
  • 272
5

using c++17, you can write

template <class T, class... Ts, class = std::enable_if_t<(std::is_same_v<T, Ts> && ...)>
void fun(T x, Ts... xs)
{
}
doraemon
  • 2,296
  • 1
  • 17
  • 36
4

The code below will work using C++20 Concepts. Compile with the -std=c++20 flag.

#include <concepts>
#include <vector>

enum Maiden {
  Eunice
  , Beatrice
  , Una_Brow
  , Helga
  , Aida
};

using dragon_list_t = std::vector<Maiden>;

dragon_list_t make_dragon_list(std::same_as<Maiden> auto... xs) {
  return {xs...};
}

int main(int argc, char *argv[])
{
  make_dragon_list(Helga,Aida,Aida);
  return 0;
}

This uses the std::same_as concept from the C++20 standard library. You can also use the std::convertible_to concept if you want more flexibility.

user2023370
  • 10,488
  • 6
  • 50
  • 83
2

This is almost direct alternative to (Maiden...).

dragon_list_t make_dragon_list()
{
    return dragon_list_t();
}
template<typename ...T>
auto make_dragon_list(Maiden first, T &&... other) -> decltype(make_dragon_list(std::forward<T>(other)...))
{
    //here be dragons
    return {first, std::forward<T>(other)...};
}

but it still does not support the construction by multiple variables. If Mained was a class, that can be constructed from 2 variables, when

make_dragon_list({1,0}) 

will work, but

make_dragon_list({1,0}, {2,3}) 

won't compile.

This is why this proposal so important

jenkas
  • 872
  • 14
  • 16
1

I think the following code is helpful for your case:

template <class...>
struct IsAllSame {};

template <class T, class B1>
struct IsAllSame<T, B1> {
  static constexpr const bool kValue = std::is_same<T, B1>::value;
};

template <class T, class B1, class... Bn>
struct IsAllSame<T, B1, Bn...> {
  static constexpr const bool kValue =
      IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false;
};

IsAllSame<int>::kValue == true
IsAllSame<bool, int>::kValue == false
IsAllSame<bool, int, int>::kValue == false
Nan Hua
  • 3,414
  • 3
  • 17
  • 24
1

It really depends what you're trying to implement, exactly.

Usually enum indicates runtime subtypes of a particular class, or a discriminated union (boost::variant). But in this case, you want to pass the enum directly. Moreover, you have a limited set of possible values, and each function call forms a subset. Really what you are representing is one subset, not several parameters at all.

The best way to represent a subset of a finite set is a bitset. Large sets should use std::bitset; small sets can just use unsigned long.

enum Maiden_set {
    Eunice = 1,
    , Beatrice = 2
    , Una_Brow = 4
    , Helga = 8
    , Aida = 16
};

dragon_list_t make_dragon_list(Maiden_set) {
    //here be dragons
}

make_dragon_list( Eunice + Beatrice + Helga );

or, since you appear to want to handle variation at compile time,

template< int Maidens > // parameter is not a Maiden_set because enum+enum=int
dragon_list_t make_dragon_list() {
    //here be dragons
}

make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int

It should be possible to generate the powers of 2 automatically using an operator+ overloaded on the enum type. But I'm not sure I'm on the right track at all.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • You can compile-time bitshift, that is, `enum { first_value = 1, second_value = 1 << 1, third_value = 1 << 2 };` – Puppy Sep 13 '10 at 20:15
  • @DeadMG: I'd write it that way, but you'd be surprised how many readers have never seen that operator before. – Potatoswatter Sep 13 '10 at 20:20
  • I have, but out of curiosity, would there be any concerns about bit-endianness? I know that's probably obscure. I don't know what CPUs that would be an issue with, and it's not like I'm trying to build something that's 100% cross-platform or anything. Just curious. – Brett Rossier Sep 13 '10 at 20:38
  • 1
    Well, since you don't care which bit is set, only that each flag corresponds to one bit, then there's no reason that endianness or portability should break the code. As long as you don't try to set more bits than exist in the backing type, you're fine. – Puppy Sep 13 '10 at 20:47
  • 1
    @pheadbaq: No, a bitset encoding is just a number like any other, so (Eunice+Beatrice+Helga) = 1+2+8 = 11 is no more endianness-sensitive than simply writing the number 11. – Potatoswatter Sep 13 '10 at 21:14
1

In short, you should probably just create a vector. It isn't that much overhead, especially if you use something like boost::list_of or C++0x's initializer list. The syntactic overhead is minimal, and it is more flexible (you could pass list with a number of arguments known only at runtime).

If you really wanted, you could use variadic template parameters to do this:

// Using pass-by-value since I'm assuming it is primitive:

template< typename T, typename... Args>
void make_dragon_list_internal( dragon_list_t* dragon_list, T t, Args... args )
{
   // add T to dragon_list.
   make_dragon_list_internal( dragon_list, args... );
}

void make_dragon_list_internal( dragon_list_t* dragon_list )
{
   // Finalize dragon_list.
}

template<typename... Args>
dragon_list_t make_dragon_list( Args... args )
{
  dragon_list_t dragon_list;
  make_dragon_list_internal( &dragon_list, args... );
  return dragon_list;
}

It's typesafe, and extensible (you could make this take things other than dragons, if you feel like).

Todd Gardner
  • 13,313
  • 39
  • 51
1

I would try to keep things simple, and the simplest solution I can think of is just using a plain old vector. By using C++0x features you can get a syntax that is similar to (even if not exactly) what you want:

void foo( std::vector<int> const & v ) {
   std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ") );
}
int main() {
  foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {}
}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

C++20 concepts makes it as nice as

template<std::same_as<Maiden>... T>
dragon_list_t make_dragon_list(Maiden...) {
    //here be dragons
}

https://gcc.godbolt.org/z/EKzWME

Dvir Yitzchaki
  • 487
  • 4
  • 13
0

A more general way to do this, without extra assumptions (like they are one certain, specific type) is to use a modern C++20+, requires clause, which to me sounds like the next best thing to having this built into the language in a more intuitive way.

Ideally, you could use std::same_as<Ts...> (concept) or std::is_same<Ts...> (type-trait), but in actuality you cannot because the standard doesn't provide them as variadic for some reason. (I'm not sure why; it's kind of annoying.)

So we will implement our own and then maybe get around to writing a proposal for the standard, because they are fundamental. We'll call them all_same (concept) and are_same (type-trait).

Here's how to use them:

template<typename... Ts> requires all_same<Ts...>
someFunction(Ts... ts){}

Which is pure C++20 style, using the concept. Or, a mix of C++20 and C++17:

template<typename... Ts> requires are_same_v<Ts...>
someFunction(Ts... ts){}

If you're stuck in C++17, then you could use SFINAE:

template<typename... Ts, std::enable_if_t<std::are_same_v<Ts...>, void>>
someFunction(Ts... ts){} // I feel so dirty

Implementations

C++20 Concept version.

template<typename... Ts>
concept all_same =
    (std::same_as<typename std::tuple_element_t<0, std::tuple<Ts...>>, Ts> && ...);

C++17 type-trait version.

template<typename... Ts>
struct are_same : std::integral_constant<bool, (std::is_same_t<typename std::tuple_element_t<0, std::tuple<Ts...>>, Ts> && ...)>
{};

template<typename... Ts>
inline constexpr bool are_same_v = are_same<Ts...>::value;

The implementation of our variadic are_same type-trait is written in terms of the binary std::is_same type trait. This could be quite useful for pre-C++20 code, but then of course you won't be able to use the requires keyword for it. Still useful by using SFINAE (std::enable_if) in lieu of concepts and C++20, and for other metaprogramming tasks.

What both of these do is use the tuple template facilities--which are metaprogrammatic in nature--to extract information about the type of the first element of the variadic pack, and then compare that against all the other types in the pack to ensure they are identical. There is no runtime overhead. This is all compile time.

void testFunction()
{
    someFunction(4, 38, 58); // Fine.
    someFunction("hello", "world", "how are you?"); // Fine.
    someFunction("wtf", 28, 482.3); // Error!
}

The first two work because all arguments are the same type. The third one fails with a compiler error because they aren't.

Daniel Russell
  • 578
  • 4
  • 9