28

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'

Xaver Kapeller
  • 49,491
  • 11
  • 98
  • 86
Nicholas Hamilton
  • 10,044
  • 6
  • 57
  • 88
  • 3
    I would guess that variadic functions were added to C with the sole purpose of supporting the printf family of functions, which must be type-unsafe. The format-string I/O concept itself was probably just taken from C's predecessors like BCPL (see https://en.wikipedia.org/wiki/BCPL). In modern C++, there is no need to introduce type-safe variadic functions, because we have superior language constructs anyway, especially since C++11. Unfortunately I have no references for my guesses. It would be interesting to ask this question to Bjarne Stroustrup himself. – Christian Hackl Aug 28 '15 at 12:37
  • 1
    You can do `void foo(double *)` and call it by `foo((double[]){1,2,3,4,5})`. Need GNU C++ extension. – user3528438 Aug 28 '15 at 12:50
  • Is'nt that feature too anecdotic to be worth incorporating in an already overfeatured language ? Then you should as well claim `void foo(double ..., int ..., double ...)` and the like. –  Aug 28 '15 at 13:04
  • 7
    @ChristianHackl: There's no fundamental reason why the printf family must be type-unsafe. C could have declared that the implementation first pushes a "type token" on the call stack so the vararg mechanism can check that the right type of value is on the stack. That would have slowed down correct code, and C historically had a strong preference of fast above safe. – MSalters Aug 28 '15 at 13:05
  • 1
    @MSalters: OTOH, that still would not make it type-safe at compile time. – Christian Hackl Aug 28 '15 at 13:08
  • @YvesDaoust Actually, I think that would be pretty useful... – Nicholas Hamilton Aug 28 '15 at 13:10
  • @MSalters I think gcc prints a warning when printf/scanf argument is mismatched to the format specifier, I'm sure I've seen it. – sashoalm Aug 28 '15 at 13:12
  • @nicholashamilton: I managed to program in C and C++ for 25 years without ever feeling the need for a variadic function. –  Aug 28 '15 at 13:14
  • @YvesDaoust yeah well, the convenience of functional languages.... – Nicholas Hamilton Aug 28 '15 at 13:21
  • @sashoalm: Indeed. It only works if the string is a constant, though. Which is precisely why printf as specified cannot be safe at compile time. – MSalters Aug 28 '15 at 13:22
  • @MSalters It does show how a little pragmatism could have been a great help, though - this warning could have been incorporated into K&R C easily (it's not much of a technical challenge), and would have covered 99.99% of the bugs. Instead they wanted to be purists and not allow special treatment for printf/scanf. – sashoalm Aug 28 '15 at 13:42
  • 2
    @user3528438 `template using id = T; void foo(double*); foo(id{1,2,3,4});` works fine w/o extensions. – dyp Aug 28 '15 at 18:08
  • @MSalters: A type-safe version could probably have been about as cheap and in many cases cheaper than the hack that was implemented if the print function took a compiler-generated `const char*` describing the parameters and compilers could choose the passing means as appropriate, so given `int a; long b; print(1, 0x12345, a, b, a+b)` a compiler could generate a temp holding `a+b`, and pass a `static const char[]` containing, encoded, the integer value 1, the integer value 0x12345, the static or frame-relative address of a, the static or frame-relative address of b, and the frame-relative... – supercat Aug 28 '15 at 22:57
  • ...address of the compiler temp value. In most systems in the early days of C, pushing an two-byte integer value stored in a variable would take 4 bytes of code and pushing a long would often take twice that, but a fairly simple encoding method could reduce the overhead of pushing an `int` to two bytes (or maybe even one) if the variable was near the top of the stack frame, as would often be the case. – supercat Aug 28 '15 at 23:00

8 Answers8

23

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.

Smi
  • 13,850
  • 9
  • 56
  • 64
edmz
  • 8,220
  • 2
  • 26
  • 45
  • 5
    and the value of the non-type template parameter(s) have restrictions -- they have to be compile-time constant expressions. – Ben Voigt Aug 28 '15 at 20:31
15

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:

  • initializer lists, a short-hand syntax for a variable number of values of a given type: foo({1, 2, 3, 4, 5})
  • variadic templates, which are a beast of their own and allow writing a type-safe printf for example

Variadic 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 type
  • Args const&... to declare a pack of arguments
  • args... to unpack the pack in an expression
Cort Ammon
  • 10,221
  • 31
  • 45
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • The call to `print` doesn't make much sense. Where do the `args` go ? – Quentin Aug 28 '15 at 15:03
  • 5
    @Quentin: Variadic functions often work (today) with recursion, so you call `print` with 4 arguments, which calls `print` with 3 arguments, which calls `print` with 2 arguments, which calls `print` with 1 argument => this is the base case (non-variadic function) and the recursion stops. – Matthieu M. Aug 28 '15 at 15:19
  • I'm blind, didn't see that they were two overload of `print`. Makes way more sense now :p – Quentin Aug 28 '15 at 15:23
11

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 ;)

Community
  • 1
  • 1
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • I can't get it to work: `error C2783: 'void foo(Doubles...)': could not deduce template argument for ''` – Simon Kraemer Aug 28 '15 at 14:37
  • @SimonKraemer IIRC SFINAE is all kinds of broken on MSVC, but sadly I don't have experience in finding workarounds... – Quentin Aug 28 '15 at 14:51
  • Even in VS2015? Ok.... In the meantime I build a "workaround" myself: http://stackoverflow.com/a/32273936/4181011 – Simon Kraemer Aug 28 '15 at 14:55
  • @RyanHaining haha, yeah, C++ often starts with the most complete possible syntax, then adds shorthands for recurrent patterns. If you look at a C++03 iterator for-loop, it's pretty bulky too. – Quentin Aug 29 '15 at 11:49
2

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.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Or simply `void foo(Convertible... doubles);` (which I had to look up, but is guaranteed to work by N4377; the reason probably being that any function with a placeholder in the parameter-decl-clause is *equivalent* to / defined as a function template) – dyp Aug 28 '15 at 18:05
  • @dyp Personally I'm not a fan of that usage as now we're hiding that this is a function template. Although I look forward to all the rep I will get by answering SO questions about it :) – Barry Aug 28 '15 at 18:12
  • Oh we can make it *entirely clear* that this is a template by using the *pretty* `AllConvertibleToDouble{...T} void foo(T... t);` -- I just can't get gcc to accept `AllConvertible{...T} void foo(T... t);` unfortunately :( -- hmm now I want to define a concept named `Template` – dyp Aug 28 '15 at 18:56
  • @dyp `AllConvertible{...T}` is invalid syntax: a template-introduction needs a qualified-concept-name before the `{`. `AllConvertible` is a partial-concept-id. – Casey Sep 15 '15 at 22:50
  • Also, FWIW, `Convertible` is likely going to be named `ConvertibleTo` in accordance with LWG guidance during the July Ranges TS telecon. (Unless, obviously, the committee changes the name again ;) – Casey Sep 15 '15 at 22:52
  • @Casey Not sure I can follow you: A *qualified-concept-name* contains a *constrained-type-name* which can be a *partial-concept-id* according to [dcl.spec.auto.constr] .... ? -- edit: oh, and I see a https://xkcd.com/541/ there ;) – dyp Sep 16 '15 at 00:01
  • @dyp I had convinced myself that `qualified-concept-name := nested-name-specifier_opt concept-name`, which is very much incorrect. In any case, the concept resolution rules in [temp.constr.resolve] don't correctly specify how to form the concept argument list for a partial-concept-id used in a template introduction. I've filed [issue #92](https://github.com/cplusplus/concepts-ts/issues/92) against the concepts TS to fix it. – Casey Sep 16 '15 at 03:24
1

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

dau_sama
  • 4,247
  • 2
  • 23
  • 30
  • It's not too difficult to implement. It's in fact easier than variadic templates. However, the paper should (A) include a rationale why this is needed, and thus why `std::initializer_list` won't do, and probably (B) explain how `sizeof...` works for `double...`. Bonus points for (C) actual proposed wording for the Standard. – MSalters Aug 28 '15 at 12:54
1
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)
{
}
Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
1

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.

Community
  • 1
  • 1
jxh
  • 69,070
  • 8
  • 110
  • 193
0

Because you can use

void foo(std::vector<T> values);
SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
  • ...which is type-safe but only allows a single type... and is not very "natural" to call, either. – DevSolar Aug 28 '15 at 12:43
  • I seriously doubt this is the reason. Plus, it is not the same kind of thing at all. – juanchopanza Aug 28 '15 at 12:44
  • @juan, please elaborate. The OP wanted an analogue of a function with a variable amount of arguments, with all the arguments being of a single type (that is clearly stated). Now, what do we have here, compared to that? A variable amount of unnamed arguments? Check. Being able to count the arguments actually passed, and get their values? Check. Of course, this is not the _reason_, however, I think that a function that takes a vector of T fully meets the OPs requirement of `The user can pass a variable number of terms to the foo function, however, all of the terms must be of type T` – SingerOfTheFall Aug 28 '15 at 12:49
  • @SingerOfTheFall: To me, it sounds more like the OP wants some historical references or other theoretical background on why there is no built-in language support for such a feature. – Christian Hackl Aug 28 '15 at 13:03
  • @MSalters: Well, it sure feels very different to me than `foo(0.4, 0.3, 0.2);`. But feelings are irrational anyway :) – Christian Hackl Aug 28 '15 at 13:09