Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

Bruegels Fuckbooks posted:

A great feature, but then people have to implement it. Behold: https://devblogs.microsoft.com/cppblog/c1114-stl-features-fixes-and-breaking-changes-in-vs-2013/


The features aren't "directly" related but there's a pattern I have noticed over the years between someone having the brilliant idea to make something variadic, and absolutely hellish implementation compiler level + weird compiler bugs. If you go over the C variadic function stuff the macros all suck rear end too. It might be that there are elegant implementations that could have been done that wouldn't have caused the problems I have seen, but real world programmers are just too dumb to not gently caress up variadics.

I think you are very confused and have learned little bits and pieces of things and came to some incorrect conclusions or something. Before C++ had variadic templates, everyone faked them in a few different ways. Microsoft's approach was not unusual or particularly ugly and did not cause compatibility issues that other approaches did not have. VC++ was late to implement a lot of C++11 features, but the primary cause of that was that there was a period where Microsoft was big on the idea that .NET would entirely replace native development and so didn't have anyone working on their C++ compiler.

None of that has anything whatsoever to do with C variadic functions.

Adbot
ADBOT LOVES YOU

Bruegels Fuckbooks
Sep 14, 2004

Now, listen - I know the two of you are very different from each other in a lot of ways, but you have to understand that as far as Grandpa's concerned, you're both pieces of shit! Yeah. I can prove it mathematically.

Plorkyeran posted:

I think you are very confused and have learned little bits and pieces of things and came to some incorrect conclusions or something. Before C++ had variadic templates, everyone faked them in a few different ways. Microsoft's approach was not unusual or particularly ugly and did not cause compatibility issues that other approaches did not have. VC++ was late to implement a lot of C++11 features, but the primary cause of that was that there was a period where Microsoft was big on the idea that .NET would entirely replace native development and so didn't have anyone working on their C++ compiler.

None of that has anything whatsoever to do with C variadic functions.

All of these statements are entirely correct and I would agree with you.

Maybe I was a little unclear in my little earlier posts and I'll give you guys some context. I have noticed a pattern with variadic XXX - whether that XXX be functions or templates or whatever. Whenever I see that word, my heart dies a little inside because it reminds me of bugs I had to fix in legacy code when changing compiler versions early in my career. I remember spending days reading over how the VA_LIST poo poo worked in C and being like "jesus christ, who came up with that." Similarly, I encountered a C++ codebase where I had to upgrade a visual studio version for a codebase I wasn't super familiar with and having a very bad experience there (I wasn't complaining specifically about visual studio in this story, it was intended to be an illustration that this poo poo is complicated and can go wrong and that post was helpful and light went off when I read about it.). These are two separate, awful memories in my early career, interspersed with a couple of cases where variadic such and such didn't work on such on such platform for stupid reasons, and now I groan whenever I see the word variadic in a computer programming context.

SAVE-LISP-AND-DIE
Nov 4, 2010
Lol just lol if you don't define 0, 1 and 2-arity overloads for all your reducer functions :smug:

Ola
Jul 19, 2004

What is the big draw of variadic functions anyway? Academic circle jerk? Perceived efficiency gain if you don't count increased complexity against it?

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
It's for printf (and scanf).

There's a short list of other things that you'd use variadic functions for, but they're all pretty inconsequential - the only reason people bother with them is that variadic functions already exist to support printf and so you might as well use them.

Ola
Jul 19, 2004

Jabor posted:

It's for printf (and scanf).

There's a short list of other things that you'd use variadic functions for, but they're all pretty inconsequential - the only reason people bother with them is that variadic functions already exist to support printf and so you might as well use them.

Right. So if you were designing it from the ground up, it would be a good time to go screw it, we're not having it. I can see it in bash, but that's just because I'm used to terminal commands with arbitrary modifiers. It doesn't have to be that way.

I haven't written much C++, but as far as I can tell from the docs, it's just number formatting that you could do with a string library before you gave the string to printf. Perhaps left aligning and so on depends on whatever system thing printf is printf'ing to, but it's obviously something you could access outside printf, if printf can. Is printf in C very different from that?

SAVE-LISP-AND-DIE
Nov 4, 2010
In all seriousness, the 0 and 1-arity variants let you avoid exception handling and providing default values when reducing on empty or single value lists.

In nearly-JS pseudocode of summing a list of numbers:

code:
[1,2,3].reduce((x, y) => return x + y)     => 6
[1].reduce((x, y) => return x + y)         => Error, no x
[].reduce((x, y) => return x + y)          => Error, no x or y
If you define overloads for the 0 and 1 arity case of the anonymous lambda, returning the "default" value for 0-arity "() => return 0", and the value as-is for 1-arity "(x) => return x", the output for the above code would be 6, 1 and 0 respectively.

It's not a world-shatteringly useful feature but it is nice to have.

Edit: oops multi-arity isn't variadic, my bad.

SAVE-LISP-AND-DIE fucked around with this message at 13:16 on Jun 26, 2020

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt


It's easier to just always use fold instead of reduce, i.e. provide an extra argument with the initial state. Depending on your language, you can just make it automatically default to the array's element type's default value.

Pseudocode:

code:
function reduce<T, S>(
      xs : collection<T>, 
      reducer: (S, T => S), 
      initialState : S = default<S>
) { ... }

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Ola posted:

Right. So if you were designing it from the ground up, it would be a good time to go screw it, we're not having it. I can see it in bash, but that's just because I'm used to terminal commands with arbitrary modifiers. It doesn't have to be that way.

I haven't written much C++, but as far as I can tell from the docs, it's just number formatting that you could do with a string library before you gave the string to printf. Perhaps left aligning and so on depends on whatever system thing printf is printf'ing to, but it's obviously something you could access outside printf, if printf can. Is printf in C very different from that?

printf in C is the same thing. The complication is that C doesn't have strings.

Xarn
Jun 26, 2015

Ola posted:

I haven't written much C++, but as far as I can tell from the docs, it's just number formatting that you could do with a string library before you gave the string to printf. Perhaps left aligning and so on depends on whatever system thing printf is printf'ing to, but it's obviously something you could access outside printf, if printf can. Is printf in C very different from that?

Apart from your approach worsening the performance significantly, there is a bunch of things that are fundamentally variadic, e.g. tuples, and you need to handle it somehow. C's varargs are terrible because they lack compile time checking, not because variadic.

RPATDO_LAMD
Mar 22, 2013

🐘🪠🍆
The only thing you should ever actually write a new variadic function for
is if you're creating a wrapper function around printf for debug logging or whatever.

Athas
Aug 6, 2007

fuck that joker

Xarn posted:

Apart from your approach worsening the performance significantly, there is a bunch of things that are fundamentally variadic, e.g. tuples, and you need to handle it somehow.

I don't think this follows. Tuples are foundational to most statically typed functional languages, and they usually do not support variadic functions (although Haskell lets you emulate them, sort of, with type classes).

Athas fucked around with this message at 07:28 on Jun 27, 2020

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


RPATDO_LAMD posted:

The only thing you should ever actually write a new variadic function for
is if you're creating a wrapper function around printf for debug logging or whatever.

The only variadic function I ever wrote allowed C code to call a C++ logging API like fprintf. Thank the powers for Stack Overflow cause I never would've sorted that out on my own.

Xerophyte
Mar 17, 2008

This space intentionally left blank

Athas posted:

I don't think this follows. Tuples are foundational to most statically typed functional languages, and they usually do not support variadic functions (althoug Haskell lets you emulate them, sort of, with type classes).

In a functional language you conceptually only have two arities: unary and nullary. All you in principle need for an arbitrary number of arguments is to construct a function that will return a value when done or "itself" (or a very similar function) when it has more args to consume. Variadic ends up being more a matter of if the type system lets you do all that without choking.

Ola
Jul 19, 2004

Xarn posted:

Apart from your approach worsening the performance significantly, there is a bunch of things that are fundamentally variadic, e.g. tuples, and you need to handle it somehow. C's varargs are terrible because they lack compile time checking, not because variadic.

Tuples aren't fundamentally variadic, they can be hard limited to any number. "If you need a 17-tuple, it's time to make a type." the compiler might say. And how does it worsen performance? Does the string formatting inside printf happen for free?

Xarn
Jun 26, 2015
That's still variadic, you just placed a really low implementation limit on the N :v:

Anyway, the performance overhead is caused by having to allocate for every single argument. At least that's how I understood your proposed replacement for print(f) family of functions -- replace variadic printf with a function that puts together a format string and an array of already formatted strings (which need to be allocated).

Xarn
Jun 26, 2015

Athas posted:

I don't think this follows. Tuples are foundational to most statically typed functional languages, and they usually do not support variadic functions (although Haskell lets you emulate them, sort of, with type classes).

If I have a tuple, I have a variadic function, even if I have to wrap it inside a tuple first!


This post brought to you by trying to have two variadic packs in a C++ function :suicide:

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Product types aren't variadic. They're fundamentally different things.

KaneTW
Dec 2, 2011

Tuples aren't variadic at all.

Functional languages generally compile (a_1,...,a_n) to a uniquely defined n-tuple type (e.g. Haskell has mkTupleTy :: [Type] -> Type). Up to some high n, library implementations provide typeclass instances for your tuple. Above that, you really shouldn't be using tuples anyhow (or add your own).

Even without that, all tuples with n>=2 can be represented by nested 2-tuples: (a_1, (a_2, (a_3, ...))). In fact, that's how generic representations of types do things; decompose into product (a,b)/sum Either a b/unit ().


If a function expects a n-tuple, you cannot pass a m-tuple with m /= n [unless your tuple implementation is via dependent types and the function is polymorphic over n I guess]

VikingofRock
Aug 24, 2008




I'm forever impressed that Haskell people managed to get printf working in their super strict type system without variadic functions. (explanation for how it works)

The type safe version is probably the one you should actually use, but IMO it's a little less interesting because it's more obvious how it works.

KaneTW
Dec 2, 2011

It's pretty cool what you can do with types. I was binding Godot to Haskell a while back and implemented static inheritance with typeclasses.

Unfortunately not the most idiomatic thing, but I need to rewrite that anyways once linear types are out.


If anyone's curious, Godot.Api.Auto has the automatically generated typeclasses and instances, and Godot.Methods has method names.

The Method typeclass matches names and classes into a signature, and HasBaseClass encodes inheritance itself.

There's also a reflexive and transitive extension for HasBaseClass (:<) and some custom type errors so the typechecker doesn't complain when you mess up a method call.



To some extent, this does belong in the thread here. But you have to make some sacrifices when binding an OOP game engine to Haskell.

KaneTW fucked around with this message at 20:38 on Jun 27, 2020

1337JiveTurkey
Feb 17, 2005

If your usual language doesn't have enough varargs for you, Scala allows multiple (curryable) parameter sections. So a normal declaration would be:

code:
def touch(butts: Butt*): Unit = {
	// Blah blah blah
}
But it's also possible to do this:
code:
def touch(butts: Butt*)(moreButts: Butt*)(yetMoreButts: Butt*): Unit = {
	// Blah blah blah
}
Another interesting feature of Scala parameter blocks is that if the last parameter block is a => SomeType (a thunk taking no parameters and returning a value), you can use special syntactic sugar to write it as a plain old brace delimited block.

code:
def time[T](timeThis: => T): T = {
	// Some timing code that calls timeThis.apply() in the middle.
}

time {
	calculateFoo();
}
It's nifty but Scala has a lot of nifty syntactic sugar that has got to be hell for people who've never touched the language before.

Doc Hawkins
Jun 15, 2010

Dashing? But I'm not even moving!


wtf, Scala doesn't just autocurry all ordered parameters?

KaneTW
Dec 2, 2011

Yeah, JVM limitations afaik?

Xarn
Jun 26, 2015
I might have my terminology wrong, but the way I understand variadic types is that a variadic type can be parametrized over an arbitrary number of types. This then makes e.g. tuple or variant variadic, while something like a pair (is always parametrized with two types) is not.

Ola
Jul 19, 2004

Xarn posted:

I might have my terminology wrong, but the way I understand variadic types is that a variadic type can be parametrized over an arbitrary number of types. This then makes e.g. tuple or variant variadic, while something like a pair (is always parametrized with two types) is not.

It's a function that takes an arbitrary number of arguments. A pair has two arguments, but they can of course be the same type. Tuples might seem like they can have an arbitrary number of arguments, but there may be a hard limit somewhere in the stock language, which means it has none of the problems of implementing actual variadic stuff and every n-tuple is a rock solid type up to whatever limit there is for n. I like 17, it's a nice and arbitrary number.

Also, isn't parsing a seemingly arbitrary number of arguments into an array of strings cheating, like Java or Bash? It's not completely variadic, it's a single array of strings. It does seem like academic circlejerk, but the circlejerking academics don't mind if you implement it safely. That is perhaps the best lesson learned from printf.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
In Java (at least in the printf case) it's an array of the top type, not an array of string.

C doesn't have a top type, so that's out the window. I guess you could require every printf parameter to be a pointer to the actual thing you want to print (which is, when you look under the hood, effectively how Java does it), but that's cumbersome to use if you need to take addresses explicitly, and having the compiler magically do that for you isn't very in keeping with the C ideology.

Ola
Jul 19, 2004

Jabor posted:

In Java (at least in the printf case) it's an array of the top type, not an array of string.

Yeah, I meant the command line arguments, (String[] args).

urea
Aug 18, 2013

Xarn posted:

I might have my terminology wrong, but the way I understand variadic types is that a variadic type can be parametrized over an arbitrary number of types. This then makes e.g. tuple or variant variadic, while something like a pair (is always parametrized with two types) is not.

I'm not even close to being an expert, but I think you can say that tuples are parametrized by a list of types, and then defined inductively on the structure of the list? So in c++ you pattern match on the variadic template arguments' head and tail.
Or in a functional language you would use a GADT/inductive family. So I think technically a list of types is just one type (or sort, or whatever). But i think variadic functions are just functions that take a list of arguments, where you don't explicitly construct the list, so I dont know if the distinction matters...

urea fucked around with this message at 16:21 on Jun 28, 2020

Foxfire_
Nov 8, 2010

A tuple is usually just shorthand for a perfectly ordinary type. If I have a C++ function that takes a std::tuple<int, float, string> parameter and I try to call if with a std::tuple<int, float, float>, I'll get a compile error because those types don't match. I could have just as well defined defined a struct type with those fields, the std::tuple type is just shorthand.

C/C++-style variadic functions defer the type-checking to runtime, if it exists at all. If I call printf("int=%d, float=%f, string=%s\n", 5, 1.0f, 2.0f), I won't get a compile-time error* and in C/C++, there won't be a runtime type check, it's just undefined behavior. It acts something like a python-esque language where there's no static type checking, but also without guaranteed runtime errors for wrong types

Java printf() effectively takes an array of Object, just with some syntactic sugar to create the array and box anything that wasn't Object already. That only works since it has a conversion from any type -> Object, and all Object have a ToString(), which is all printf() needs to use. You can't make a function that takes any number of nonhomogeneous types (i.e. something like python's struct.pack() that that takes a format string and some data and returns a byte array with those values packed into it)


* for printf, you probably do get a warning on modern compilers, but that's just because they have special case code to recognize printf() formats specifically.

Ola
Jul 19, 2004

If C was made today, I am convinced you could make a type safe, non-variadic "print to console" function that was every bit as a fast as printf. It was a fair mistake made at the time, there is no need to repeat it.

Foxfire_ posted:

You can't make a function that takes any number of nonhomogeneous types

Which begs the question why do you want to make something that seems like you can, with terrible consequences if you get it wrong, instead of making it easy to use with clearly defined rules that says what you can or can't? The most likely answer is academic circlejerk, with "sins of the fathers" a close runner up. Even if you write C today, you don't have to acknowledge or emulate it in any way, you can write whatever you want as type safe as you should.

PS:


Foxfire_ posted:

A tuple is usually just shorthand for a perfectly ordinary type. If I have a C++ function that takes a std::tuple<int, float, string> parameter and I try to call if with a std::tuple<int, float, float>, I'll get a compile error because those types don't match. I could have just as well defined defined a struct type with those fields, the std::tuple type is just shorthand.

Is there not some limit of n for std::tuple<type 1, type 2, type 3 ... type n> in C++?

RPATDO_LAMD
Mar 22, 2013

🐘🪠🍆

Ola posted:

If C was made today, I am convinced you could make a type safe, non-variadic "print to console" function that was every bit as a fast as printf. It was a fair mistake made at the time, there is no need to repeat it.
Rust has one, but the compile times are horrendous. Rust's println is implemented as a procedural macro so there's a lot of actual code execution during compile time.
There was a thing where somebody tried to compile a file with 1000 lines of println!("Hello world!"); and a VPS with even 1tb of ram was choking on it.

quote:

Is there not some limit of n for std::tuple<type 1, type 2, type 3 ... type n> in C++?
With a proper variadic template implementation there shouldn't be one. Someone mentioned earlier Microsoft used to just fake it with copy-pasted code for each N from 1 to 10, so it wouldn't work for something larger if you compiled with MSVC.

It's not variadic-related but Rust still does that same thing with methods on char arrays. The language has no support for "length of the array" as a template parameter so they just have all the std lib implementations copy-pasted for "array of 1 character", "array of 2 characters", etc up to 32.

RPATDO_LAMD fucked around with this message at 22:00 on Jun 28, 2020

urea
Aug 18, 2013

Ola posted:

Is there not some limit of n for std::tuple<type 1, type 2, type 3 ... type n> in C++?

It's defined recursively on the variadic template arguments, so if there were a limit, it would be based on the recursion limit of the compiler's template instantiator , not some fundamental limit.

Ola
Jul 19, 2004

Print to console takes one string argument, the stock tuples go up to 17, boom, done. The world is a better place.

RPATDO_LAMD
Mar 22, 2013

🐘🪠🍆

Ola posted:

Print to console takes one string argument, the stock tuples go up to 17, boom, done. The world is a better place.

Stock tuples should only go up to 3 or 4
anything longer and you should define an actual named struct or record type or whatever.

Ola
Jul 19, 2004

RPATDO_LAMD posted:

Stock tuples should only go up to 3 or 4
anything longer and you should define an actual named struct or record type or whatever.

I am willing to accept this compromise, even if 17 is the most arbitrary value of n known to mankind.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...

Ola posted:

I am willing to accept this compromise, even if 17 is the most arbitrary value of n known to mankind.

Listen bud, it's 2^4th, then one more for good measure just because of all the whining if it were merely 16. I hope you don't want 18, because gently caress you if you do.

Foxfire_
Nov 8, 2010

I was being dumb above; Java gets variadic's of heterogeneous types exactly the same way python does:
- Take all the excess parameters and dump them into a single array of generic objects
- Do a runtime type test when they are accessed and fail then if they're wrong

It abandons static type safety just like the C version (python never had static safety to begin with). C/C++ doesn't have a way to implement the runtime type test and has uglier syntax, but is otherwise the same.

How do Rust/Haskell/etc.. that have static type-safe variadic's work?

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Ola posted:

If C was made today, I am convinced you could make a type safe, non-variadic "print to console" function that was every bit as a fast as printf. It was a fair mistake made at the time, there is no need to repeat it.

It wasn't a mistake at the time. Yes, using modern technology we can do better (cf. the Haskell example linked to earlier), but those approaches all depend on things that hadn't been invented when C was created. And given the relative costs of computer time and programmer time in the 1970s, printf's design was absolutely the right choice.

Adbot
ADBOT LOVES YOU

RPATDO_LAMD
Mar 22, 2013

🐘🪠🍆

Foxfire_ posted:

I was being dumb above; Java gets variadic's of heterogeneous types exactly the same way python does:
- Take all the excess parameters and dump them into a single array of generic objects
- Do a runtime type test when they are accessed and fail then if they're wrong

It abandons static type safety just like the C version (python never had static safety to begin with). C/C++ doesn't have a way to implement the runtime type test and has uglier syntax, but is otherwise the same.

How do Rust/Haskell/etc.. that have static type-safe variadic's work?
I'm a rust newbie but here's my understanding of how it works in Rust:

Every printable type has to implement a function named fmt (inside the Display trait) that writes to a "formatter" object (basically just a character buffer).

At compile time the variadic macro in println!("The cat is {} years old", 7) is transformed by find and replace macro magic into basically something like this (fake code):
And yes, the format string has to be known at compile time.
code:
buf = "The cat is ";
7.printtobuffer(buf);
" years old".printtobuffer(buf);
print(buf);
But with more cleverness to avoid string copying.

So it basically works the same way as C++'s iostream.

RPATDO_LAMD fucked around with this message at 00:06 on Jun 29, 2020

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply