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
Cybernetic Vermin
Apr 18, 2005

Symbolic Butt posted:

my guess is that APL people tend to be the kind of programmer who can create usable prototype algorithms in a fast way

it's not a feature of APL per se, but just a different programming standard that is useful for the industry

this p. much, finance is packed with first-mover advantages, if you make sure all your data is readily consumable in apl (or j/a/k) and have people practiced at it you can build the tools to get into some new type of instrument from one day to the next

imho to some extent a feature of apl and friends in that there is huge efficiency in working on a codebase which fits in your field of vision

Adbot
ADBOT LOVES YOU

Blinkz0rz
May 27, 2001

MY CONTEMPT FOR MY OWN EMPLOYEES IS ONLY MATCHED BY MY LOVE FOR TOM BRADY'S SWEATY MAGA BALLS

Sapozhnik posted:

Go and JavaScript are the current hipste

ya with rust as the "so underground it hurts" hipste

DONT THREAD ON ME
Oct 1, 2002

by Nyc_Tattoo
Floss Finder
i dont think rust is very underground. i feel like it has a ton of momentum right now, it's just that it's super ambitious and has a long way to go before it's really production ready.

dragon enthusiast
Jan 1, 2010
you could say it has a lot of rust to shed

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
so, I lost the source code to a really simple tool I wrote for work, and I thought to myself: let's rewrite it in c++! so that I can finally learn some c++14

what the tool does is simple: reads from stdin or a list of files, xors them with a fixed rolling key, writes the result to output. the temptation is to structure it like this:

C++ code:
int main()
{
	vector<unsigned char> buffer(...);

	while (!feof(stdin)) {
		fread(..., stdin);
		decode(...);
		fwrite(..., stdout);
	}
}
how inefficient! while we're decoding a block, we can already start reading again from the file, and while we're writing, we can read from input and decode at the same time. there are many ways to do this, and they're all bad. I'll pick the least bad, where the code is still readable: I'll use multithreading!

I need a I/O bound thread to do the reading, a CPU bound thread to do the decoding, and a third I/O bound thread to write to stdout. no multiplexing, gently caress multiplexing, that's the OS's work. I already have one thread (the main thread) and I'll use it for writing, so that the whole thing will work like I started two subprocesses in a pipe to read what comes out. what I don't have, and I will have to write first, is a pipe. for how useful pipes are, no framework seems to implement them, and the c++ stdlib is no exception. so let's write our first class!

C++ code:
#include <deque>
#include <variant>

template<class... T, class AllocatorT = std::allocator<std::variant<T...>>>
class object_pipe
{
private:
	std::deque<std::variant<T...>> m_queue;
};
gently caress. first problem: variant is c++17 and nobody implements it yet, not even under experimental. second problem, probably solvable but gently caress it: the template parameter pack must be at the end of the template parameter list. I'd have to move the allocator type in front, but then I couldn't default it. christ, what a good start

many hours later:

C++ code:
#include <cassert>
#include <cstdlib>

#include <deque>
#include <mutex>
#include <condition_variable>
#include <type_traits>

// if you want the pipe to support multiple object types, you'll have to pass
// variant<...> as the T parameter. this isn't bad after all, you can even make
// the class runtime polymorphic using any as its element
template<class T, class AllocatorT = std::allocator<T>>
class object_pipe
{
public:
	// pretend we're a grownup class with standard-sounding typedefs
	using value_type = T;
	using allocator_type = AllocatorT;

private:
	// this is the pipe. after long internal deliberation, I decided to make
	// it a queue of pointers, instead of objects, because the queue will be
	// accessed inside a lock, and unique_ptr is nothrow movable and has an
	// extremely efficient and predictable move constructor. this saves us
	// from calling any methods of value_type while holding the lock, copy
	// and move constructors and destructors included, which may take
	// arbitrarily long and do all sorts of unpredictable and dangerous
	// things that shouldn't be done in locks
	std::deque<std::unique_ptr<value_type>, allocator_type> m_queue;

	// pipes are shared between threads (it's the whole point!!!) and that
	// means one thing: LOCKS
	std::mutex m_queue_mutex;

	// if the pipe is empty but not closed, a reader thread has to hold the
	// mutex but wait at the same time: how?!?! well keep it in your pants
	// pal, because that's what condition variables are for. this ~*magic*~
	// primitive lets you atomically drop the lock on the mutex and wait for
	// a notification, and when the notification comes, atomically move you
	// from the notification's wait queue to the mutex's. sounds complicated
	// but it's actually the most intuitive locking primitive
	std::condition_variable m_queue_changed;

	// the pipe's eof condition could be a special object we stuff in the
	// queue, but it would have to have two special properties: it never
	// actually comes out of the queue, once there it stays there, and it
	// would force our write method to look for it in the queue to know
	// when the pipe is closed and writes should start failing. not to
	// mention how complicated it would make the queue! so, it's just a
	// boring old bool
	bool m_eof;

public:
	void write(value_type&& value)
	{
		// I learned that rvalue references lose their rvalueless at the
		// slightest provocation, and that without move here we'd
		// actually copy the object, not move it. forward would do the
		// same thing, but it's more for template functions that don't
		// know the kind of references they might be receiving in
		// arguments; it's kind of a hassle to use it outside of a
		// template function, in fact
		write(std::make_unique<value_type>(std::move(value)));

		// anyway, what we do here is move (or copy) value, so that it's
		// free to throw all sorts of nasty exceptions and do all sorts
		// of dodgy poo poo in its constructors and destructor, and it
		// won't affect our critical region's runtime characteristics.
		// in the lock, we'll only deal with golden star, teacher's pet
		// standard class unique_ptr. we then pass the pointer to the
		// newly copied/moved object to the unique_ptr overload below,
		// which does all the work

		// oh god I just realized we should allocate the object copy
		// with our allocator. let's leave it as a TODO because it's
		// horribly complicated!
	}

	// in case the user already has a pointer to the object, no copy needed,
	// they can pass the pointer directly to us and we'll steal it from
	// them. I won't bother with making object_pipe also support shared_ptr
	// unless someone can think of a really good reason, and I can't: you
	// write objects to a pipe to make copies of them, not to share
	// ownership with someone (and if you really wanted to, why not just
	// write a shared_ptr to the pipe?)
	void write(std::unique_ptr<value_type>&& p)
	{
		// loving null pointers always trying to sneak in
		if (!p) {
			// :fuckoff:
			throw std::exception();
		}

		{
			std::lock_guard<std::mutex> lock(m_queue_mutex);

			// if the eof flag is set, we can no longer write
			// anything. we can make m_eof atomic so that it can be
			// checked outside of the lock (safe, because once it's
			// set to true, it can never go back to false. why?
			// because I have just decided it can't), which isn't
			// useful here, but it can spare us a potentially
			// expensive copy/move in the by-rvalue overload above.
			// we won't though because gently caress it
			if (m_eof) {
				// we'll define an appropriate exception later
				throw std::exception();
			}

			// yoink! steal ownership of *p from the caller and give
			// it to ourselves. good job unique_ptr! *licks gold
			// star sticker*
			m_queue.emplace_back(std::move(p));
		}

		// we changed the queue, so we notify the change to whoever may
		// be listening (i.e. any pending readers). we do this outside
		// of the lock so that a pending reader can immediately start
		// running instead of leaving one wait queue for another. we
		// notify readers one at a time because they'll queue behind the
		// mutex anyway. this has consequences for the read method,
		// though, that we'll see later
		m_queue_changed.notify_one();
	}

	// the usual emplace method, to construct the value from its essential
	// Saltes, that an ingenious Programmer may have a whole Ark of Noah in
	// his own calling function
	template<class... Args>
	void emplace(Args&&... args)
	{
		// it's incredibly tedious to write the same boilerplate over
		// and over and over and... so many ellipses and I can never
		// remember where they go in the syntax. count your blessings,
		// me, because in bad old C++03 this wasn't even possible
		write(std::make_unique<value_type>(std::forward<Args>(args)...));
	}

	// closes the write end of the pipe, setting the eof flag
	void close_write()
	{
		{
			std::lock_guard<std::mutex> lock(m_queue_mutex);

			// closing twice is an error! maybe we really should
			// make m_eof atomic and use double-checked locking? or
			// maybe our users should refrain from performing
			// illegal operations, so that we won't have to optimize
			// our failure paths? how's it sound, users?
			if (m_eof) {
				// we'll define an appropriate exception later
				throw std::exception();
			}

			m_eof = true;
		}

		// changing the eof flag counts as changing the queue, because
		// the eof flag is like a special object we enqueue
		m_queue_changed.notify_one();
	}

	// unique_ptr's commendable behavior lets us pop objects off the read
	// end of the pipe efficiently and without breaking the strong exception
	// guarantee: ten points for Gryffindor!
	std::unique_ptr<value_type> read()
	{
		// this will contain the next object read from the queue
		std::unique_ptr<value_type> p;

		{
			// this time, we can't use lock_guard, because we need
			// to transfer ownership of the lock (we'll see later
			// why). we'll use the movable analogue of lock_guard,
			// unique_lock. having to specify std::mutex every time
			// is an annoying side effect of how template argument
			// deduction works, and since we can't expect every
			// single template class to have a make_xxx factory
			// function to do the deduction from the arguments,
			// C++17 will finally fix this, and let us write std::
			// unique_lock lock(m_queue_mutex) instead. C++17 isn't
			// stable or even fully implemented yet, so I won't use
			// it. again, this is nitpicking: at least we have auto
			// now. still, these remaining instances of redundant
			// type names are all the more glaring now that we have
			// auto
			std::unique_lock<std::mutex> lock(m_queue_mutex);

			// how are condition variables used? what are they for?
			// well Virginia, they are the best synchronization
			// primitive ever: they let you wait on any condition
			// you can express in code. how is it possible, surely
			// the scheduler can't execute your code to check the
			// condition? you might ask (but won't because you are
			// a node.js scrub and don't even know what a scheduler
			// is. I bet you haven't even written your own scheduler
			// once :smug:). turns out it's not necessary! first we
			// check if the condition is met, in this case that
			// there's an object (or an eof flag) to read...
			while (!(!m_queue.empty() || m_eof)) {
				// if it isn't met, we wait for a change
				// notification...
				m_queue_changed.wait(lock);

				// (note that we let the condition variable
				// borrow the lock: if no notification is
				// pending, the condition variable will have to
				// suspend the thread; before suspending, it
				// will release the lock, because suspending
				// while holding a lock is another name for a
				// deadlock. it will need to borrow the lock
				// from us to release it, and that's why we're
				// using unique_lock instead of lock_guard)

				// ... and try again
			}

			// now let's see what condition woke us up. is it an
			// object in the queue?
			if (!m_queue.empty()) {
				// in the write method we write to the back, so
				// here we'll read from the front. we'll use
				// move assignment to steal the pointer from the
				// queue
				p = std::move(m_queue.front());

				// a debug-only check for non-nullity is OK
				// here, there's no risk of data corruption or
				// anything, the user will just dereference a
				// null unique_ptr and get an exception
				assert(p);

				// remove the unusable, moved-from entry from
				// the queue
				m_queue.pop_front();
			}
			// the queue is empty, is the eof flag set?
			else if (m_eof) {
				// signaling eof with an exception actually
				// makes users of this class simpler!
				throw std::exception();
			}
			// here's something I don't like about C++: I have to
			// check explicitly that neither check succeeded, even
			// if it's "impossible". I can think of a way to avoid
			// this, with a frightening chain of lambdas, variadic
			// templates etc. but gently caress it
			else {
				// abort, not terminate. abort is noisier
				std::abort();
			}
		}

		// if we're here, we changed the queue (popped off the front),
		// so we have to notify whatever other reader is waiting for
		// a change (I don't recommend multiple readers). sometimes
		// we'll wake it spuriously: we may have removed the last
		// element from the queue, so the pending reader will wake,
		// reacquire the lock, find that the queue is empty (and the eof
		// flag clear), and will have to sleep again immediately. such
		// is life
		m_queue_changed.notify_one();

		// like before, a debug-only check is fine
		assert(p);
		return p;
	}

	// if value_type can be returned by value safely (through a nothrow
	// move), this method is also available
	std::enable_if_t<
		std::is_nothrow_move_constructible<value_type>::value,
		value_type
	>
	read_value()
	{
		return *read();
	}

	// this closes the read end of the pipe
	void close_read()
	{
		{
			std::lock_guard<std::mutex> lock(m_queue_mutex);

			// dang! we have to split the eof flag into two separate
			// flags, write_closed and read_closed, so that the two
			// ends can be closed independently but no more than
			// once, and closing either end sets the eof flag. for
			// now let's leave it as a TODO and allow closing the
			// read end multiple times
			m_eof = true;

			// since nobody is going to read from this pipe any
			// longer, discard any objects still in it
			m_queue.clear();
		}

		// as usual, changing the eof flag counts as changing the queue
		m_queue_changed.notify_one();
	}

	// you know what's another great reason to make the eof flag an atomic
	// that can be safely read outside of the critical region? making
	// methods like this const. or I could make the mutex mutable, it's not
	// like an *actually* const mutex is in any way usable as a mutex. yes I
	// think that's what I'll do, or rather, TODO he he he (the joke is that
	// my editor highlights the word TODO)
	bool is_eof()
	{
		// this method lets you call read in a loop like while(!pipe.
		// is_eof()) pipe.read(...), if you're really that exception-
		// averse
		std::lock_guard<std::mutex> lock(m_queue_mutex);
		return m_eof && m_queue.empty();
	}
};
well that's not too bad is i-:suicide:

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

hackbunny posted:

so, I lost the source code to a really simple tool I wrote for work, and I thought to myself: let's rewrite it in c++! so that I can finally learn some c++14

what the tool does is simple: reads from stdin or a list of files, xors them with a fixed rolling key, writes the result to output. the temptation is to structure it like this:

C++ code:
int main()
{
	vector<unsigned char> buffer(...);

	while (!feof(stdin)) {
		fread(..., stdin);
		decode(...);
		fwrite(..., stdout);
	}
}
how inefficient! while we're decoding a block, we can already start reading again from the file, and while we're writing, we can read from input and decode at the same time. there are many ways to do this, and they're all bad. I'll pick the least bad, where the code is still readable: I'll use multithreading!

I need a I/O bound thread to do the reading, a CPU bound thread to do the decoding, and a third I/O bound thread to write to stdout. no multiplexing, gently caress multiplexing, that's the OS's work. I already have one thread (the main thread) and I'll use it for writing, so that the whole thing will work like I started two subprocesses in a pipe to read what comes out. what I don't have, and I will have to write first, is a pipe. for how useful pipes are, no framework seems to implement them, and the c++ stdlib is no exception. so let's write our first class!

C++ code:
#include <deque>
#include <variant>

template<class... T, class AllocatorT = std::allocator<std::variant<T...>>>
class object_pipe
{
private:
	std::deque<std::variant<T...>> m_queue;
};
gently caress. first problem: variant is c++17 and nobody implements it yet, not even under experimental. second problem, probably solvable but gently caress it: the template parameter pack must be at the end of the template parameter list. I'd have to move the allocator type in front, but then I couldn't default it. christ, what a good start

many hours later:

C++ code:
...lots of code...
well that's not too bad is i-:suicide:

here i made you one in java
Java code:
boolean done = false;
BlockingQueue<Thing> queue = new LinkedBlockingQueue<>();
while (!done && !queue.isEmpty()) {
    thing = queue.take();
}
yw, no license go nuts

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
yosposting at its finest :thumbsup: :discourse:

Bloody
Mar 3, 2013

how is what you posted different from a SPSC queue eg http://www.boost.org/doc/libs/1_62_0/doc/html/boost/lockfree/spsc_queue.html

Bloody
Mar 3, 2013

i ask in large part because i use spsc queues as pipes frequently unless i have no idea what a pipe is, what a spsc queue is, or

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

hackbunny posted:

yosposting at its finest :thumbsup: :discourse:

ty friend

Volte
Oct 4, 2004

woosh woosh
there's no such thing as a "simple tool" written in C++

Symbolic Butt
Mar 22, 2009

(_!_)
Buglord
jesus christ hackbunny :stare:

gonadic io
Feb 16, 2011

>>=

Sweeper posted:

here i made you one in java
Java code:

boolean done = false;
BlockingQueue<Thing> queue = new LinkedBlockingQueue<>();
while (!done && !queue.isEmpty()) {
    thing = queue.take();
}

yw, no license go nuts

gonadic io
Feb 16, 2011

>>=

hackbunny posted:

// signaling eof with an exception actually
// makes users of this class simpler!

i'm not sure i want to use this code if it'll make me simpler

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

e: nevermind

spsc_queue is what I should use instead of deque. the difference between object_pipe and spsc_queue is that spsc_queue is entirely wait-free, which means you have to add a mutex and condition variable yourself if you want to wait until some data is available. which makes it a replacement for deque (for queue actually, because spsc_queue is not double-ended), not object_pipe

hackbunny fucked around with this message at 17:12 on Nov 29, 2016

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
I'm morbidly curious about how they made a lock-free queue, which is notoriously harder than a lock-free stack, but I doubt the implementation is human-readable

e: oh easy, they use a ringbuffer instead of a linked list

hackbunny fucked around with this message at 17:19 on Nov 29, 2016

gonadic io
Feb 16, 2011

>>=
in the spirit of fairness and reinventing the wheel buggily, feel free to mock my current stupid spare time project (a "game" in Rust): https://github.com/djmcgill/Vox-Machina

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

gonadic io posted:

in the spirit of fairness and reinventing the wheel buggily, feel free to mock my current stupid spare time project (a "game" in Rust): https://github.com/djmcgill/Vox-Machina

how has perf been with rust? I've been wary of pushing it at work as an experimental thing since I have no idea how it performs in non-micro contexts

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

hackbunny posted:

I'm morbidly curious about how they made a lock-free queue, which is notoriously harder than a lock-free stack, but I doubt the implementation is human-readable

e: oh easy, they use a ringbuffer instead of a linked list

ring buffers are gods data structure/thing of concurrent helpfulness

gonadic io
Feb 16, 2011

>>=

Sweeper posted:

how has perf been with rust? I've been wary of pushing it at work as an experimental thing since I have no idea how it performs in non-micro contexts

Performance equivalent to c++ is one of its major features so go nuts on that front, lots of no-overhead poo poo.

It is past 1.0 and released now, but the infrastructure and 3rd party langs aren't the most mature right now so bear that in mind

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
C++ code:
// this is not an emptyquoted block of code: rather, this is my post, in the
// form of comments to A Code. a little like literate programming if you think
// about it, except bad. yosprogramming (c) hackbunny 2016 do not steal!!!

#include <cassert>
#include <cstdlib>

#include <deque>
#include <mutex>
#include <condition_variable>
#include <type_traits>

template<class T, class AllocatorT = std::allocator<T>>
class object_pipe
{
public:
	using value_type = T;
	using allocator_type = AllocatorT;

private:
	// PSA: using a mutex around a deque is a terrible way to do what I'm
	// doing. boost's lock-free SPSC queue uses a circular buffer backed by
	// a fixed-size array, and I believe Java's equivalent uses multiple
	// chunks, independently locked (and if it's not the queue that does
	// this, there's another thread-safe container that does). this solution
	// is already massively overengineered for the amount of data it deals
	// with, so we'll stay with the dumb monkey implementation
	std::deque<std::unique_ptr<value_type>, allocator_type> m_queue;

	// like I promised, this is now mutable so that const instances of
	// object_pipe can use the lock too
	std::mutex mutable m_queue_mutex;

	std::condition_variable m_queue_changed;

	// I realized that object_pipe can grow without bounds, while the
	// behavior of a UNIX pipe is that writers will block if the pipe is
	// "full". let's add a maximum size parameter, that defaults to the
	// maximum capacity of our backing queue
	std::size_t const m_max_size = m_queue.max_size();

	// I promised I would split the eof flag and I did. the two ends of the
	// pipe can now be closed independently, but each end can only be closed
	// once
	bool m_closed_write;
	bool m_closed_read;

private:
	// the eof flag is now the closed flag, because "eof" proper is when the
	// pipe is closed *and* empty. named xxx_unlocked after getc_unlocked,
	// indicating that the lock must be acquired beforehand
	bool is_closed_unlocked() const
	{
		return m_closed_write || m_closed_read;
	}

	// this flag may be useful to poll the read end without blocking
	bool is_empty_unlocked() const
	{
		return m_queue.empty();
	}

	// finally, meet the new and improved eof flag
	bool is_eof_unlocked() const
	{
		return is_closed_unlocked() && is_empty_unlocked();
	}

	std::size_t max_size_unlocked() const
	{
		assert(m_max_size <= m_queue.max_size());
		return m_max_size;
	}

	bool is_full_unlocked() const
	{
		return m_queue.size() >= max_size_unlocked();
	}

public:
	void write(value_type&& value)
	{
		// TODO we should use our allocator here, ugh. I don't know how,
		// yet. why doesn't unique_ptr natively support allocators?!
		write(std::make_unique<value_type>(std::move(value)));
	}

	// and I guess this unique_ptr should be specialized for our allocator
	// too? and we should write a make_unique static method for this class
	// that returns appropriately specialized allocators??? or should it be
	// a member method??? can I copy the allocator or should all unique_ptrs
	// share the same allocator instance through... what, a shared_ptr? and
	// what will allocate the shared_ptr??? jesus christ I hate allocators
	void write(std::unique_ptr<value_type>&& p)
	{
		if (!p) {
			throw std::exception();
		}

		{
			// now that write can block too (if the queue is full),
			// write, too, will need to wait on the condition
			// variable, which means unique_lock instead of
			// lock_guard
			std::unique_lock<std::mutex> lock(m_queue_mutex);

			// if the queue is full, wait until it's no longer full.
			// exit early if the queue is closed
			while (!is_closed_unlocked() && is_full_unlocked()) {
				// aren't you glad that we made read signal the
				// condition variable too, instead of chickening
				// out and using notify_all? we can safely block
				// here, secure in the knowledge that, if a
				// reader came along and freed space in the
				// queue, we would awaken
				m_queue_changed.wait(lock);
			}

			if (is_closed_unlocked()) {
				throw std::exception();
			}

			assert(!is_full_unlocked());
			m_queue.emplace_back(std::move(p));
		}

		m_queue_changed.notify_one();
	}

	// non-blocking and timed variants of write raise lots of questions!
	// like if the write would block (or times out), what to do with the
	// object that wasn't written? as always, my answer is "gently caress it"

	template<class... Args>
	void emplace(Args&&... args)
	{
		write(std::make_unique<value_type>(std::forward<Args>(args)...));
	}

	void close_write()
	{
		{
			std::lock_guard<std::mutex> lock(m_queue_mutex);

			if (m_closed_write) {
				throw std::exception();
			}

			m_closed_write = true;
			assert(is_closed_unlocked());
		}

		m_queue_changed.notify_one();
	}

	std::unique_ptr<value_type> read()
	{
		std::unique_ptr<value_type> p;

		{
			std::unique_lock<std::mutex> lock(m_queue_mutex);

			while (is_empty_unlocked() && !is_closed_unlocked()) {
				m_queue_changed.wait(lock);
			}

			if (!is_empty_unlocked()) {
				assert(!is_eof_unlocked());
				p = std::move(m_queue.front());
				assert(p);
				m_queue.pop_front();
			}
			else if (is_closed_unlocked()) {
				assert(is_eof_unlocked());
				throw std::exception();
			}
			else {
				// assert(0) is a semi-standard way of saying
				// that a branch is unreachable
				assert(0);
				std::abort();
			}
		}

		m_queue_changed.notify_one();

		assert(p);
		return p;
	}

	// read_value died on its way back to its home planet

	// inspired by boost's SPSC queue, I'll add a method to receive the next
	// object without messing with smart pointers or move constructors that
	// may throw etc.
	template<class Consumer>
	void consume(Consumer consumer)
	{
		consumer(*read());
	}

	void close_read()
	{
		{
			std::lock_guard<std::mutex> lock(m_queue_mutex);

			if (m_closed_read) {
				throw std::exception();
			}

			m_closed_read = true;
			assert(is_closed_unlocked());

			m_queue.clear();
			assert(is_eof_unlocked());
		}

		m_queue_changed.notify_one();
	}

	// thanks to the new, mutable mutex, this method can now be const
	bool is_eof() const
	{
		std::lock_guard<std::mutex> lock(m_queue_mutex);
		return is_eof_unlocked();
	}


};

hackbunny fucked around with this message at 18:06 on Nov 29, 2016

Sapozhnik
Jan 2, 2005

Nap Ghost
i'm more concerned about why you'd ever want to apply a "rolling xor key" to a file, ever, particularly since you use the word "key"

there is literally no situation where this is ever the correct thing to be doing to some poor innocent piece of data. other than perhaps the very first example in the very first lecture of a cryptanalysis 101 course

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

Sapozhnik posted:

i'm more concerned about why you'd ever want to apply a "rolling xor key" to a file, ever, particularly since you use the word "key"

there is literally no situation where this is ever the correct thing to be doing to some poor innocent piece of data. other than perhaps the very first example in the very first lecture of a cryptanalysis 101 course

don't ask

champagne posting
Apr 5, 2006

YOU ARE A BRAIN
IN A BUNKER

don't roll your own crypto.

(u less you are iot things)

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

Sapozhnik posted:

i'm more concerned about why you'd ever want to apply a "rolling xor key" to a file, ever, particularly since you use the word "key"

there is literally no situation where this is ever the correct thing to be doing to some poor innocent piece of data. other than perhaps the very first example in the very first lecture of a cryptanalysis 101 course

an xor can make a good enough fingerprint depending on the number of bytes

fingerprint not being security related

Sweeper fucked around with this message at 19:24 on Nov 29, 2016

Soricidus
Oct 21, 2010
freedom-hating statist shill

Boiled Water posted:

roll your own crypto.

it's fun, and what could possibly go wrong?!?!?!?!?!

Wheany
Mar 17, 2006

Spinyahahahahahahahahahahahaha!

Doctor Rope
besides, if you make your own crypto it's unbreakable because nothing else uses it!

Xarn
Jun 26, 2015

hackbunny posted:

lots of code

Always use condition_variable::wait overload that takes a predicate, its neat. Also I am not entirely sure about your cv notifications, I think you can simplify them since you are assuming SPSC, but gently caress reasoning about threads after 6 beers.

---
Also I think the case of calling cv::notify under mutex is common enough to have optimized implementation, but ymmv.

Soricidus
Oct 21, 2010
freedom-hating statist shill

Wheany posted:

besides, if you make your own crypto it's unbreakable because nothing else uses it!

well exactly. no nsa backdoors, guaranteed.

Sapozhnik
Jan 2, 2005

Nap Ghost
u guys realize they're called concurrency primitives for a reason right

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Sapozhnik posted:

u guys realize they're called concurrency primitives for a reason right

yes, it's so primitives can use them

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

Xarn posted:

Also I am not entirely sure about your cv notifications, I think you can simplify them since you are assuming SPSC,

simplify, or suppress spurious notifications? because "notify always no matter what" seems simple enough. it's hardly the most pressing issue though: what about the fact that I'm doing some allocations without the specified allocator?!

but seriously the lack of variant is going to majorly loving suck

Xarn posted:

Also I think the case of calling cv::notify under mutex is common enough to have optimized implementation, but ymmv.

I know and I have written such an implementation at the dawn of time, but going by cppreference, it's not guaranteed

gonadic io
Feb 16, 2011

>>=
i looked variant up, and

"The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or it holds no value"

this is only in c++17?? :eyepop:

Xarn
Jun 26, 2015

hackbunny posted:

simplify, or suppress spurious notifications? because "notify always no matter what" seems simple enough. it's hardly the most pressing issue though: what about the fact that I'm doing some allocations without the specified allocator?!

but seriously the lack of variant is going to majorly loving suck

Supress spurious notifications, allocators can suck it. Also look at the bright side of this, C++17 is not that far off. :v:


gonadic io posted:

i looked variant up, and

"The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or it holds no value"

this is only in c++17?? :eyepop:

Do you know how many ways there are to bikeshed a variant? Because if not, look standardization process around std::variant and std::optional :v:

gonadic io
Feb 16, 2011

>>=
i forgot that c++, like haskell, follows the design-by-mailing-list approach

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

gonadic io posted:

this is only in c++17?? :eyepop:

not just that, it hasn't even been officially implemented yet. I can #include <experimental/option> but I can't #include <experimental/variant>. I think maybe boost has it but I'm trying to learn standard C++, not boost

it couldn't have been done earlier than c++11 or 14 either because of language and stdlib defects (although all compilers had proprietary workarounds)

and don't ask how pattern matching is done because you don't want to know. c++, much like java, suffers from chronic nameism. a lot of the work done for c++11 and since has been aimed at gradually reducing the need to name so many things

Xarn posted:

Supress spurious notifications, allocators can suck it

eh, I'll make it a TODO. bespoke artisanal variant first. allocators can suck it, otoh I never learned allocators and this is first and foremost a learning experience

Xarn posted:

Do you know how many ways there are to bikeshed a variant? Because if not, look standardization process around std::variant and std::optional :v:

variant is incredibly complicated and I'm not surprised it took a long time to agree on an interface. I tried writing my own but I couldn't figure out how to write the non-member get<> method. I looked at how it was defined for tuple and came out with a headache. we either need an iterative style for templates or at least a declarative syntax that isn't so drat abstract and verbose. maybe around 2030 or so, provided they'll have agreed on concepts by then

MrMoo
Sep 14, 2000

gonadic io posted:

i looked variant up, and

"The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or it holds no value"

this is only in c++17?? :eyepop:

There is a lot of stuff that is surprisingly c++17 or later, I was recently surprised with map::insert_or_assign.

For a spiffy SPSC queue read you a Lynx Queue, using page faults around a ring buffer to avoid socket contention on the head/tail pointers.

Slurps Mad Rips
Jan 25, 2009

Bwaltow!

gonadic io posted:

i looked variant up, and

"The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or it holds no value"

this is only in c++17?? :eyepop:

i wish i could show you the massive amount of discussion regarding "what if exceptions are thrown during copy constructors when copying the variant" and what people wanted to do ("well just allocate. works for me!", "let the program be in a bad state", "just call abort", "throw a nested exception, who cares"). All because boost::variant has the never empty guarantee, and people wanted that to be the default. (so default constructing a variant default constructs the first type in its template list)

the fact that a compromise was reached among the committee's different factions so quickly regarding this is nothing short of a miracle

btw hackbunny, i feel yr pain regarding the allocator thing. makes it a nightmare to make a generic soa_vector type, so you're stuck with either putting the allocator first, or doing some really wacky and weird poo poo with template aliases, or even template templates.

hackbunny posted:


eh, I'll make it a TODO. bespoke artisanal variant first. allocators can suck it, otoh I never learned allocators and this is first and foremost a learning experience


might i point you to a library I wrote so people on C++11 can get all the tasty goodness of future library features? it actually started life in this thread like 3 years ago when famdav (maybe?) asked about a polymorphic deep copying type and i wrote a type as a joke (hilariously enough, there's now a polymorphic_value<T> type being submitted to the committee). now it exists in some form or another at bittorrent, apple, mongodb (lol), and a german telecom startup deployed an early version of it to a few thousand boxes.

allocators are butts and not the good and smooth and gay kind. we might end up getting saved by a 19 year old german kid who is currently designing a more extensive less bullshit system of allocators, but i dont think we'll ever fix the realloc problem

hackbunny posted:

variant is incredibly complicated and I'm not surprised it took a long time to agree on an interface. I tried writing my own but I couldn't figure out how to write the non-member get<> method. I looked at how it was defined for tuple and came out with a headache. we either need an iterative style for templates or at least a declarative syntax that isn't so drat abstract and verbose. maybe around 2030 or so, provided they'll have agreed on concepts by then

the get is the easy part, its the loving matching thats is a nightmare, and i still dont know why they didnt make visitation just take no more than 2 variants, because that is easy to implement, but no we have to have a function go first and then any variable number of variants instead of the other way around. (im so mad that overload didn't make it in because it would ease my suffering)

all i want is my lil intrusive smart pointer proposal to get into c++20 and i will die a happy dumb gay nerd

Plorkyeran
Mar 22, 2007

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

hackbunny posted:

not just that, it hasn't even been officially implemented yet. I can #include <experimental/option> but I can't #include <experimental/variant>. I think maybe boost has it but I'm trying to learn standard C++, not boost

boost has had it for 14 years, but std::variant is significantly different (mostly better, sometimes just different). part of why it's taken so long to get into the standard is because boost.variant had a bunch of issues that were nontrivial to resolve, while the boost things that landed earlier only had relatively trivial problems

Adbot
ADBOT LOVES YOU

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

Slurps Mad Rips posted:

allocators are butts and not the good and smooth and gay kind. we might end up getting saved by a 19 year old german kid who is currently designing a more extensive less bullshit system of allocators, but i dont think we'll ever fix the realloc problem

what's the realloc problem?

I stopped caring about allocators when I found they couldn't be written as adapters for lots of common allocators, and EA's paper on their gamedev stl validated me. it seems some (all?) of those issues were finally addressed and I pleasantly note that deallocate's size argument can now be, basically, ignored (still no idea why it even has a size argument)

Slurps Mad Rips posted:

the get is the easy part

huh, you're right, I was overthinking it. I'm still very unfamiliar with type_traits, vararg templates etc. because back in my day we had to trait our types uphill both ways etc.

except for one thing:

C++ code:
v->template cast<I>()
what fresh new hell is this?

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