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
omeg
Sep 3, 2012

If your OS doesn't have memory allocation APIs that let you specify whether the memory should be reserved or committed then lol.

Adbot
ADBOT LOVES YOU

ullerrm
Dec 31, 2012

Oh, the network slogan is true -- "watch FOX and be damned for all eternity!"

omeg posted:

If your OS doesn't have memory allocation APIs that let you specify whether the memory should be reserved or committed then lol.

Sadly, Linux is the main offender here. Userland apps don't have a way to force commit. After all, there's basically two system calls available on *NIX to ask for more address space:

* brk() -- no flag parameters, the kernel decides whether it's committed or not
* mmap() -- there's flags to explicitly request reservation-without-commit, but no flag for mandatory-commit.

The decision to whether or not to overcommit is actually made at a system-wide level for all processes. And you don't want to force a commit by simply touching all the pages as part of each malloc... if the Linux kernel has overcommit turned on, and actually hits an "I don't have physical pages left to back this" condition, it actually uses a heuristic to decide what process to kill to free up pages... and its decision may not necessarily be the process that was asking for the page.

It's why nearly every server package meant to run on Linux will have a big warning as part of their installation instructions to turn off autocommit.

crazypenguin
Mar 9, 2005
nothing witty here, move along

ullerrm posted:

Sadly, Linux is the main offender here. Userland apps don't have a way to force commit.

I don't know if it's documented behavior, but one thing I know the JVM does is use mmap() to allocate memory, then immediately re-mmap the allocated memory to the same place this time using MAP_FIXED. This seems to cause the (re)allocation to fail if there is insufficient available memory for the whole thing.

However, this doesn't actually reserve the memory for the app, it just seems to actually force the system to be okay with returning an allocation failure right then, regardless of over commit settings.

Suspicious Dish
Sep 24, 2011

2020 is the year of linux on the desktop, bro
Fun Shoe
and this is why fork/exec is trash

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Systems would probably overcommit even without fork/exec; they just make it almost mandatory.

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

If you don't overcommit for large mmap then you're sort of being dumb, I think.

Plorkyeran
Mar 22, 2007

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

rjmccall posted:

Systems would probably overcommit even without fork/exec; they just make it almost mandatory.
How does OS X get away without it then?

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Plorkyeran posted:

How does OS X get away without it then?

Er. OS X definitely over-commits. To test my sanity, I just wrote a tiny program, and malloc happily gave me well over 300GB in 64K chunks before I killed it out of boredom. As far as I know, OS X malloc will keep returning pointers until it exhausts the user address space.

iOS has different rules, but I'm pretty positive mmap still doesn't guarantee you physical memory.

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
Huh. I've had multiple people tell me that OS X doesn't overcommit and I've never had a reason to investigate. The lack of any upper limit on the swap file size other than available disk space probably makes it mostly irrelevant in practice.

MrMoo
Sep 14, 2000

From what I can see OS X and iOS have memory pressure so applications can be smarter, and FreeBSD 9 possibly only recently gained overcommit functionality.

Over commit is there because it improves performance with dumb apps, BSD developers usually think they are amazing and their OS is the best already so they don't need it.

o.m. 94
Nov 23, 2009

I'm new to VS (2015). So far I've just been compiling a bunch of simple C++ code. But I want to start using data files (e.g. txt). I've got a directory structure for my program like this:

code:
myproject/
    build/
        vs2015/myproject
    codebase/
        data/
        art/
        src/
All my C++ files live in /src and I will want to use assets from /data and /art. The VS .sln and all the stuff that gets generated when I build lives in /build/vs2015/myproject, in an attempt to keep all the VS stuff away from my code.

However, at the moment I get myproject.exe generated at /build/vs2015/myproject/Debug. What I would like is a seperate folder somewhere:


code:
data
art
myproject.exe
Which is effectively the codebase/ folder minus the source, plus the executable. Any paths I have defined for something like myfile.open("data/foo.txt") should treat this folder as the CWD because that's where the executable runs from?

Alternatively, is there any documentation / reading out there on this? Maybe the approach is wrong. I want to be able to build and just zip it up and give it out.

raminasi
Jan 25, 2005

a last drink with no ice
What exactly is your question? It's fairly simple to set the binary output path in the project options (Linker, I believe), in the same way that you've managed to redirect your source directory.

Doc Block
Apr 15, 2003
Fun Shoe
Or just change directory at run-time to the "codebase" directory. You could have it be passed in as an argument that, if present, the executable will change to the specified directory. That way, for testing/debugging within VS you can have it change to the "codebase" directory, and then for shipping you can just copy the release executable to the copy of "codebase" that you're shipping.

Doc Block fucked around with this message at 18:05 on May 31, 2015

o.m. 94
Nov 23, 2009

Sorry, I gotcha. I was just overthinking it

The Laplace Demon
Jul 23, 2009

"Oh dear! Oh dear! Heisenberg is a douche!"

o.m. 94 posted:

I'm new to VS (2015). So far I've just been compiling a bunch of simple C++ code. But I want to start using data files (e.g. txt). I've got a directory structure for my program like this:

code:
myproject/
    build/
        vs2015/myproject
    codebase/
        data/
        art/
        src/
All my C++ files live in /src and I will want to use assets from /data and /art. The VS .sln and all the stuff that gets generated when I build lives in /build/vs2015/myproject, in an attempt to keep all the VS stuff away from my code.

However, at the moment I get myproject.exe generated at /build/vs2015/myproject/Debug. What I would like is a seperate folder somewhere:


code:
data
art
myproject.exe
Which is effectively the codebase/ folder minus the source, plus the executable. Any paths I have defined for something like myfile.open("data/foo.txt") should treat this folder as the CWD because that's where the executable runs from?

Alternatively, is there any documentation / reading out there on this? Maybe the approach is wrong. I want to be able to build and just zip it up and give it out.

What you want to do is correct. Add the files to your project and under properties, set the "Copy to Output Directory" property. See MSDN (File Properties > Copy to Output Directory Property).

JawKnee
Mar 24, 2007





You'll take the ride to leave this town along that yellow line
I'm writing a couple of clients and a server for a networking class that requires that the server and clients be able to handle sending/receiving concurrently. This hasn't been a problem for my server, or my TCP client, however I've run into a snag with my UDP client.

Prior to calling the two functions below, I have fork()ed a child process, and assigned it to the sendBytes function which handles reading from a file in pre-determined sized blocks, and sends via datagrams. Once it's finished sending it pipes a message to the parent process, the message contains an int for how many block of data had been sent - I did this so that the parent function would know how many blocks of data at most it could expect to receive. While the child is functioning, the parent (which has been assigned to the recieveBytes function) should be reading from the same socket in a non-blocking fashion, and writing any received data blocks to an output file.

So far so good, providing all data that has been sent is received the client works. However I need to provide for the possibility that the final data block (or some other data block) might get dropped and never returned by the server. To deal with that I made the recvfrom calls non-blocking in the parent function, and piped a message (also non-blocking) to the parent function from the child telling it the total data packets it could expect to receive. Still all good. However if a packet is dropped, I want to program to allow for a timeout period and then close the socket so it isn't sitting there waiting forever - I've tried using a sleep() call in the child process (once it is done sending) to allow for a timeout and then forcibly close the socket using a shutdown() call - however the sleep() call is blocking the parent from proceeding.

I think (though I'm not sure) that this is because both parent and child processes are sharing the same socket file descriptor, and the sleep call is blocking the use of that by the parent - is there any way around this?

The child process gets the sendBytes function:
code:
//Read bytes from file inp_fd, send bytes through socket sd, use output of pipe pfd to send packet totals to receiveBytes, pfd2 currently not used

int sendBytes(int inp_fd, int sd, struct sockaddr_in sad, int *pfd, int* pfd2) 
{
	int nIncomingBytes, nOutGoingBytes, alen, bytes_sent, pckts_sent; 
	char buf[1000];									//once working, needs to be changed to be set by user input

	bytes_sent = 0;
	pckts_sent = 0;

	memset(buf, 0, sizeof(buf));
	nIncomingBytes = read(inp_fd, buf, sizeof(buf)); //Initial read in - sizeof(buf) chars
	
	alen = sizeof(sad);
	while(nIncomingBytes > 0)
	{		
		nOutGoingBytes = sendto(sd, buf, sizeof(buf), 0, (struct sockaddr*)&sad, alen); //send to server
		bytes_sent += nIncomingBytes; 						//accumulated for later
		pckts_sent++;								//accumulated to be sent to recieveBytes via pipe pfd
		memset(buf, 0, sizeof(buf)); 						//reset between reads to avoid junk data when full buffer is not used
		nIncomingBytes = read(inp_fd, buf, sizeof(buf)); 			//read next block in from file
	}

	/*At this point, all packets have been sent, so we have a complete count of packets and bytes sent*/
	printf("Packets sent: %d, comprising %d bits.\n", pckts_sent, (bytes_sent*8));
	fcntl(pfd[1], F_SETFD, O_NONBLOCK); 				//make pfd[1] non-blocking, probably not necessary on this side
	write(pfd[1], &pckts_sent, sizeof(pckts_sent));			//send # of packets sent to recieveBytes parent function

	shutdown(sd, 1); 			//shutdown socket, allow for incoming packets
	sleep(5);		 		//timeout before shutdown, this should not be affecting the parent but is blocking it from proceding
	shutdown(sd, 2); 			//shutdown socket entirely
	
	printf("End of Sendable packets function\n"); //DIAG
	return 0;
}
The parent process gets the recieveBytes function:
code:
//recv datagrams through sd, read pipe output pfd[0] for packet totals from sendBytes, pfd2 currently not used

int recieveBytes(int sd, int *pfd, int* pfd2)
{
	struct sockaddr_in cad;
	int out_fd;
	int nIncomingBytes;
	int alen;
	char buf[1000];
	char *ND = "NOTDONE";
	long bytes_recvd = 0;
	int pckts_recvd = 0;
	int flag = 0; 
	out_fd = open("/home/jawknee/Test_Response_UDP.txt", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR); //output file

	fcntl(pfd[0], F_SETFD, O_NONBLOCK); //alter output fd of pipe to be non-blocking
	int piped_total = 0;

	alen = sizeof(cad);
	memset(buf, 0, sizeof(buf)); //set initial buffer to all \00

	while(nIncomingBytes = recvfrom(sd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&cad, &alen))
	{
		if(nIncomingBytes>0)
		{
			write(out_fd, buf, nIncomingBytes); //write only as much as is read (use nIncomingBytes)
			bytes_recvd += nIncomingBytes;
			pckts_recvd++;	
			memset(buf, 0, sizeof(buf));
			read(pfd[0], &piped_total, sizeof(piped_total));
			printf("Piped total: %d\n", piped_total); //DIAG
			printf("pckts_recvd: %d\n", pckts_recvd); //DIAG
			if((piped_total > 0) && (pckts_recvd == piped_total)) 
			{
				break;
			}
		}
	}

	printf("Recieved %d packets, constituting %ld bits\n", pckts_recvd, (bytes_recvd*8));
	close(sd);
	printf("End of Recievable packets function\n"); //DIAG
	return 0;
}
example output when sending a file that takes up just a little over 1 block of data to send is:

code:
Packets sent: 2, comprising 8056 bits.
Piped total: 2
pckts_recvd: 1
<both processes block for 5 seconds here>
End of Sendable packets function
Piped total: 2
pckts_recvd: 2
Recieved 2 packets, constituting 8056 bits
I'm kind of unsure how to proceed here, is there some way to put the child process to sleep without blocking the parent?

e: whoa, tables, sorry

e2: looks like maybe I'm taking the wrong approach with using sleep to enforce a timeout, I should be able to do it using a timeval struct and the SO_RECVTIMEO flag in a setsockopt() call, though I'm not sure what level I should be setting it to if my socket was originally set up as a SOCK_DGRAM socket, but this is progress at least!

e3: Yup that solved my problem like a charm. Probably should have looked at the socket options before trying to freeze everything myself. I should note though that the SO_RECVTIMEO flag requires the recvfrom call to be blocking - if it's non-blocking it will immediately return -1 ignoring the timeout duration.

JawKnee fucked around with this message at 23:53 on Jun 1, 2015

Stexils
Jun 5, 2008

What are some good beginner programming books for C? I've been using K&R's "The C programming language" but I'd like to have a second resource because I find that helps when I'm having trouble with something. All the books I've found are C++. I also tried "learn C the hard way" but it kind of sucked.

VikingofRock
Aug 24, 2008




Okay, so I'm trying to write a function which encapsulates some boilerplate around failing to parse stuff. So far I have this:

C++ code:
template <typename ParseFunc, typename... Args>
    auto try_parse(ParseFunc f, Args... args, std::string descriptor)
            -> decltype(f(args...)){
        try{
            return f(args...);
        }
        catch(std::exception& e){
            /* handle failure to parse */
        }
    }
The problem is, when I call it with this:

C++ code:
auto some_double = try_parse(std::stod, some_string, "trying to parse using std::stod");
I get a "no matching function for call to 'try_parse'" error, with a note that it ignored my try_parse() because it couldn't infer the template argument ParseFunc. After googling around, it seems like the problem is that std::stod is an overloaded function, and the compiler can't figure out which version to call. To me it seems like the compiler (clang) should be able to figure out that it should call std::stod(string). So how do I get the compiler to smarten up?

Sex Bumbo
Aug 14, 2004

VikingofRock posted:

So how do I get the compiler to smarten up?

Without just casting the function? Because that should work and otherwise I have no idea.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
The compiler isn't going to analyze your entire function body in order to figure out that you expect ParseFunc to be something callable with a particular set of arguments; it's just going to try to assign a type to that argument based on the type of the expression you pass. Since that expression is a reference to an overloaded function, it doesn't have a unique type, so the compiler treats that parameter as an undeduced context.

If you want to bake in the assumption that it takes a function pointer, you can stop using a general function type and instead write out the desired function type there, only inferring the result type. But that's tricky because of references and qualification, and it would prevent you from passing a lambda. Otherwise, you'll have to cast.

Also, the compiler won't actually deduce variadic template argument packs from the middle of argument lists; it probably could, but that's not how the feature is specified.

rjmccall fucked around with this message at 01:21 on Jun 4, 2015

VikingofRock
Aug 24, 2008




Sex Bumbo posted:

Without just casting the function? Because that should work and otherwise I have no idea.

I think that would work, but it seems like a shame because that's an extra burden on the user of try_parse(). I would prefer to have try_parse be a little smarter and figure out these overload resolutions when possible.

e:

rjmccall posted:

The compiler isn't going to analyze your entire function body in order to figure out that you expect ParseFunc to be something callable with a particular set of arguments; it's just going to try to assign a type to that argument based on the type of the expression you pass. Since that expression is a reference to an overloaded function, it doesn't have a unique type, so the compiler treats that parameter as an undeduced context.

You have to cast the function to a specific type.

Also, the compiler won't actually deduce variadic template argument packs from the middle of argument lists; it probably could, but that's not how the feature is specified.
Fine, fine, I'll cast the function. Also yeah it looks like you are right with the variadic template argument packs thing. I'll change that around too.

VikingofRock fucked around with this message at 01:20 on Jun 4, 2015

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!
Also you probably don't want the parameter to be a std::string if it's going to be used the way you're using it (which seems most likely) - you probably want a const char[]. With a std::string you'll be inadvertently constructing a string object every time you call try_parse.
(Or you could take a const std::string &, and make sure you're using an existing static std::string as the parameter each time rather than an in-place string constant, if you want to retain the convenience of a std::string parameter.)

VikingofRock
Aug 24, 2008




roomforthetuna posted:

Also you probably don't want the parameter to be a std::string if it's going to be used the way you're using it (which seems most likely) - you probably want a const char[]. With a std::string you'll be inadvertently constructing a string object every time you call try_parse.
(Or you could take a const std::string &, and make sure you're using an existing static std::string as the parameter each time rather than an in-place string constant, if you want to retain the convenience of a std::string parameter.)

This is a good catch, too. Thanks. You'd think after 6+ years of writing C++ I'd be better at remembering to take things by const reference, but I still slip up more often than I'd like to admit. Luckily this section of the code wasn't performance-critical.

o.m. 94
Nov 23, 2009

Few questions about initialization

1. Is there any difference between int foo = {0} and int foo {0}
2. Likewise, int foo = 0 and int foo(0)
3. Is it generally preferred now to assign with curly brace list init?

o.m. 94 fucked around with this message at 07:46 on Jun 4, 2015

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

JawKnee posted:

I'm writing a couple of clients and a server for a networking class that requires that the server and clients be able to handle sending/receiving concurrently. This hasn't been a problem for my server, or my TCP client, however I've run into a snag with my UDP client.

UDP is a connectionless protocol. The receiver will not get a notification when the sender closes its socket, and recvfrom() will just wait endlessly.

The OS X man page of recvfrom() mentions this explicitly:

quote:

RETURN VALUES
These calls return the number of bytes received, or -1 if an error occurred.

For TCP sockets, the return value 0 means the peer has closed its half side of the connection.
I suspect that you get ENOTCONN from your shutdown() calls, you should probably check that.

Dicky B
Mar 23, 2004

o.m. 94 posted:

Few questions about initialization

1. Is there any difference between int foo = {0} and int foo {0}
2. Likewise, int foo = 0 and int foo(0)
3. Is it generally preferred now to assign with curly brace list init?
They are equivalent, but if you replace int with auto then the story becomes more muddled. I won't bother typing it up here when I can just link you to Meyers.

http://scottmeyers.blogspot.co.uk/2014/03/if-braced-initializers-have-no-type-why.html

Joda
Apr 24, 2010

When I'm off, I just like to really let go and have fun, y'know?

Fun Shoe
If I wanted to have a collection of data of indeterminate type, I feel like the least awful solution should be one involving templates (like I'd have a map of pointers to a templated object, and then the receiver will know, based on the key, what type of data to expect.) My current implementation uses polymorphism, so I have a subclass for every type of data I want to pass around, but like I said it feels like I should be able to solve it with templates.

My problem is that when I try to use templates for this, and want a map of foo<T>* I have to provide the type of data when initialising the map. How would you do something like this, so I only have to provide type when I add an element? Is it even possible?

Xerophyte
Mar 17, 2008

This space intentionally left blank
It's possible but non-trivial, outside of casting everything to void* and being really, really careful anyhow. A generic solution will likely use or re-implement some version of boost::any.

The basic idea is that you have an untemplated base interface for your data holder that's then implemented by a template class that can hold any type. The any class itself stores a pointer to the base interface. Constructing an any object as well as getting the stored class back out will be templated, but the any class itself wont. The basic scheme is described here.

I have a hard time thinking of any scenarios where I'd want a collection of absolutely anything, though. There's always some commonality of function.

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

Xerophyte posted:

The basic idea is that you have an untemplated base interface for your data holder that's then implemented by a template class that can hold any type. The any class itself stores a pointer to the base interface. Constructing an any object as well as getting the stored class back out will be templated, but the any class itself wont. The basic scheme is described here.

From that link:
code:

return result ? *result : throw std::bad_cast();
Holy cow. I've used C++ for 23 years, and I never knew that throw is an expression, not a statement. I may abuse the poo poo out of this now I know. :pervert:

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Zopotantor posted:

From that link:
code:

return result ? *result : throw std::bad_cast();
Holy cow. I've used C++ for 23 years, and I never knew that throw is an expression, not a statement. I may abuse the poo poo out of this now I know. :pervert:

Whoa. Is it a magic top type?

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
You mean a magic bottom type, and IIRC the answer is that it didn't used to be, but C++14 (?) added some hacks to conditional expressions around it.

Slurps Mad Rips
Jan 25, 2009

Bwaltow!

rjmccall posted:

You mean a magic bottom type, and IIRC the answer is that it didn't used to be, but C++14 (?) added some hacks to conditional expressions around it.

Using a throw in an expression was mentioned in C++11 I believe, otherwise it would be really hard to error out of constexpr functions. Most stuff like string_view must be implement several functions in terms of the ternary with a throw of implementing it in C++11 instead of C++14.

JawKnee
Mar 24, 2007





You'll take the ride to leave this town along that yellow line

Zopotantor posted:

UDP is a connectionless protocol. The receiver will not get a notification when the sender closes its socket, and recvfrom() will just wait endlessly.

The OS X man page of recvfrom() mentions this explicitly:

I suspect that you get ENOTCONN from your shutdown() calls, you should probably check that.

Yeah I've restructured that part of the program entirely at this point - one function now, not handled by a separate process, recvfrom set to MSG_NONBLOCK until all data is sent, then set to block with a receive timeout set in the socket options. As a result I also don't have to use the shutdown call for the UDP side of things any longer.

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

Slurps Mad Rips posted:

Using a throw in an expression was mentioned in C++11 I believe, otherwise it would be really hard to error out of constexpr functions. Most stuff like string_view must be implement several functions in terms of the ternary with a throw of implementing it in C++11 instead of C++14.

The proposal for exceptions in the ARM (Stroustrup & Ellis) already specified throw to be an expression. But it may never have been intended to be used in that way. (I don't think they intended templates to be Turing complete either.)

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

rjmccall posted:

You mean a magic bottom type

You'd think that, living near SF, I wouldn't make that mistake.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Slurps Mad Rips posted:

Using a throw in an expression was mentioned in C++11 I believe, otherwise it would be really hard to error out of constexpr functions. Most stuff like string_view must be implement several functions in terms of the ternary with a throw of implementing it in C++11 instead of C++14.

It's always been an expression. I just thought it might not have had special type-checking rules in conditionals, but I was wrong, and even C++98 has this rule.

Subjunctive posted:

You'd think that, living near SF, I wouldn't make that mistake.

:yosbutt:

csammis
Aug 26, 2003

Mental Institution

rjmccall posted:

magic bottom type

I've never heard this expression before and Google's turning up complete nonsense. What does it mean?

FamDav
Mar 29, 2008
If you think of the expressible types in your type system, bottom is a subtype of all of those types. You can think of it as void, because any function that returns void returns nothing, except if I could actually assign the results of a void function.

It's magic because c and c++ don't have that kind of bottom, so you can't create your own throw.

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

FamDav posted:

If you think of the expressible types in your type system, bottom is a subtype of all of those types. You can think of it as void, because any function that returns void returns nothing, except if I could actually assign the results of a void function.

It's magic because c and c++ don't have that kind of bottom, so you can't create your own throw.

Actually :eng101: void is the type that is called 'unit' in functional languages. Void functions return to their caller, but the 'value' they return is not used. Bottom is the conceptual return type of functions that don't return, not even a useless value.

C++ doesn't allow the unit value to be passed around, so you can't do this:
code:
void fart(void);
void butt(void);
void dick(void)
{
  return fart(butt()); // nope
}
The value of a throw expression is treated as void syntactically (I think; at least the description of the conditional expression treats it that way). This is about as theoretically consistent as the rest of C++, so nobody really cares (except for pedants like myself :eng99:).

Adbot
ADBOT LOVES YOU

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
The type of a throw is void, but there's a special case in conditionals which says that if one operand is a throw the type is just the type of the other expression (but arbitrarily converted to an r-value)

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