Reputation: 10526
Can someone please explain to me why C++, at least to my knowledge, doesn't implement a strongly typed ellipsis function, something to the effect of:
void foo(double ...) {
// Do Something
}
Meaning that, in plain speak: 'The user can pass a variable number of terms to the foo function, however, all of the terms must be doubles'
Upvotes: 28
Views: 8316
Reputation: 300139
Historically, the ellipsis syntax ...
comes from C.
This complicated beast was used to power printf
-like functions and is to be used with va_list
, va_start
etc...
As you noted, it is not typesafe; but then C is far from being typesafe, what with its implicit conversions from and to void*
for any pointer types, its implicit truncation of integrals/floating point values, etc...
Because C++ was to be as close as possible as a superset of C, it inherited the ellipsis from C.
Since its inception, C++ practices evolved, and there has been a strong push toward stronger typing.
In C++11, this culminated in:
foo({1, 2, 3, 4, 5})
printf
for exampleVariadic templates actually reuse the ellipsis ...
in their syntax, to denote packs of types or values and as an unpack operator:
void print(std::ostream&) {}
template <typename T, typename... Args>
void print(std::ostream& out, T const& t, Args const&... args) {
print(out << t, args...); // recursive, unless there are no args left
// (in that case, it calls the first overload
// instead of recursing.)
}
Note the 3 different uses of ...
:
typename...
to declare a variadic typeArgs const&...
to declare a pack of argumentsargs...
to unpack the pack in an expressionUpvotes: 15
Reputation: 8492
There is
void foo(std::initializer_list<double> values);
// foo( {1.5, 3.14, 2.7} );
which is very close to that.
You could also use variadic templates but it gets more discursive. As for the actual reason I would say the effort to bring in that new syntax isn't probably worth it: how do you access the single elements? How do you know when to stop? What makes it better than, say, std::initializer_list
?
C++ does have something even closer to that: non-type parameter packs.
template < non-type ... values>
like in
template <int ... Ints>
void foo()
{
for (int i : {Ints...} )
// do something with i
}
but the type of the non-type template parameter (uhm) has some restrictions: it cannot be double
, for example.
Upvotes: 24
Reputation: 70472
Based on Matthew's answer:
void foo () {}
template <typename... Rest>
void foo (double arg, Rest... rest)
{
/* do something with arg */
foo(rest...);
}
If the code using foo
compiles, you know all the arguments are convertible to double
.
Upvotes: 1
Reputation: 303527
For why specifically such a thing wasn't proposed (or was proposed and rejected), I do not know. Such a thing would certainly be useful, but would add more complexity to the language. As Quentin demonstrates, there is already proposes a C++11 way of achieving such a thing with templates.
When Concepts gets added to the standard, we'll have another, more concise way:
template <Convertible<double>... Args>
void foo(Args... doubles);
or
template <typename... Args>
requires Convertible<Args, double>()...
void foo(Args... doubles);
or, as @dyp points out:
void foo(Convertible<double>... doubles);
Personally, between the current solution and the ones that we will get with Concepts, I feel that's an adequate solution to the problem. Especially since the last one is basically what you'd originally asked for anyway.
Upvotes: 2
Reputation: 5680
template<typename T, typename... Arguments>
struct are_same;
template <typename T, typename A1, typename... Args>
struct are_same<T, A1, Args...>{ static const bool value = std::is_same<T, A1>::value && are_same<T, Args...>::value;};
template <typename T>
struct are_same<T>{static const bool value = true;};
template<typename T, typename... Arguments>
using requires_same = std::enable_if_t<are_same<T, Arguments...>::value>;
template <typename... Arguments, typename = requires_same<double, Arguments...>>
void foo(Arguments ... parameters)
{
}
Upvotes: 1
Reputation: 63154
It is already possible with variadic templates and SFINAE :
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template <class... Doubles, class = std::enable_if_t<
all_true<std::is_convertible<Doubles, double>{}...>{}
>>
void foo(Doubles... args) {}
Thanks to Columbo for the nice all_true
trick. You will also be able to use a fold expression in C++17.
As later and upcoming standards are focusing on terser syntax (terse for-loops, implicit function templates...) it is very possible that your proposed syntax ends up in the Standard one day ;)
Upvotes: 11
Reputation: 4367
The way to achieve (sort of) what you suggest is to use variadic templates
template<typename... Arguments>
void foo(Arguments... parameters);
however you can pass any type in the parameter pack now. What you propose has never been implemented, maybe it could be a great addition to the language, or it could just be too difficult to implement as things stand. You could always try to write a proposal and submit it to isocpp.org
Upvotes: 1