Why C++ Is Terrifying(twitter.com)
twitter.com
Why C++ Is Terrifying
https://twitter.com/aptxk1d/status/1383122485411938306
15 comments
I find it way more annoying that std::function's type erasure--but insistence that it should be copyable--means it isn't able to commit to being movable, and so then simply can't store move-only functions; in practice I simply never want to copy an std::function, yet the type is seemingly crippled to offer that ability :/. Between this issue and how it tends to require memory allocation even in cases of bounded scope, I honestly just find std::function only useful in trivial "toy" code (or where I am going out of my way to use STL for some silly reason)... I have a few type-erasing function wrappers of my own that I use in various projects, and I do live demos for the intro CS classes I help teach wherein I quickly type out new rather-fully-featured replacements, showing the iterative implementation as you need new functionality.
folly::Function was added for exactly this reason I think https://github.com/facebook/folly/blob/master/folly/docs/Fun...
Aside from writing C++ back in my graduate role after university, I’ve avoided C++ like the plague. Glad there are alternatives to it today.
Since the HN username matches the twitter name, I'll bite: Is it the case that a raw function pointer is always trivially copyable?
I'm not super familiar with C++'s memory model, especially since they have deprecated the notion of POD and replaced it with trivially constructable.
My rudimentary thought process is that you can't just "move" raw functions because there is probably some assumptions made that a C++ function's call-site isn't going to move around if it isn't owned by an object. Otherwise, the runtime would have to deduce the location of the function call every time.
Well, on second thought, is there any reason why an int function ptr should be any different from an int ptr?
Asking as someone who has been writing C++ on-and-off for a few years for games, school, etc. but never professionally.
I'm not super familiar with C++'s memory model, especially since they have deprecated the notion of POD and replaced it with trivially constructable.
My rudimentary thought process is that you can't just "move" raw functions because there is probably some assumptions made that a C++ function's call-site isn't going to move around if it isn't owned by an object. Otherwise, the runtime would have to deduce the location of the function call every time.
Well, on second thought, is there any reason why an int function ptr should be any different from an int ptr?
Asking as someone who has been writing C++ on-and-off for a few years for games, school, etc. but never professionally.
[deleted]
I didn't fully catch your reasoning regarding the call-site of functions, but if your specifically talking about function pointers, then they are not closures so call-site doesn't matter.
> Well, on second thought, is there any reason why an int function ptr should be any different from an int ptr?
I don't know any other example, but in Itanium, a function pointer is not just an address but an (address+address) pair where the second address is a sort of a base pointer for data segment:
https://stackoverflow.com/a/18908021
I don't know any other example, but in Itanium, a function pointer is not just an address but an (address+address) pair where the second address is a sort of a base pointer for data segment:
https://stackoverflow.com/a/18908021
I don't think this is true except maybe on ancient architectures. function ptrs are usually just addresses (64 bits on most modern computers). virtual member function ptrs can have an extra bit of info to resolve the this ptr at runtime.
so usually (but not always), sizeof(int ptr) == sizeof(func ptr)
so usually (but not always), sizeof(int ptr) == sizeof(func ptr)
The answer is yes. And you can verify this by
checking std::is_trivially_copyable<FnPtrType>::value
using FnPtrType = int (*)(int) // for example
checking std::is_trivially_copyable<FnPtrType>::value
using FnPtrType = int (*)(int) // for example
Can someone knowledgeable break down into simple terms what the tweet means?
The tweet is certainly loaded, hence the point of this HN post lol.
First of all there's std::function, which uses Type Erasure https://blog.the-pans.com/type-erasure/. It means std::function<void(int)> can be the type of anything callable that takes an int and returns void (lambda, function pointer, object with operator() overload, etc.). Notice they are of different types! Hence Type Erasure.
How std::function manages its memory is poorly specified. But the standard at least states that if it's initialized from a function pointer (free, no capture), it's guaranteed that it won't allocate. https://en.cppreference.com/w/cpp/utility/functional/functio...
> When the target is a function pointer or a std::reference_wrapper, small object optimization is guaranteed, that is, these targets are always directly stored inside the std::function object, no dynamic allocation takes place.
In this case, std::function is trivially copyable. However, there's no way to know this at compile time, exactly because the type is erased in std::function.
First of all there's std::function, which uses Type Erasure https://blog.the-pans.com/type-erasure/. It means std::function<void(int)> can be the type of anything callable that takes an int and returns void (lambda, function pointer, object with operator() overload, etc.). Notice they are of different types! Hence Type Erasure.
How std::function manages its memory is poorly specified. But the standard at least states that if it's initialized from a function pointer (free, no capture), it's guaranteed that it won't allocate. https://en.cppreference.com/w/cpp/utility/functional/functio...
> When the target is a function pointer or a std::reference_wrapper, small object optimization is guaranteed, that is, these targets are always directly stored inside the std::function object, no dynamic allocation takes place.
In this case, std::function is trivially copyable. However, there's no way to know this at compile time, exactly because the type is erased in std::function.
With C++20 and the std::invocable concept, is there still much reason to use std::function?
Yes. You use std::function when you don't want every function/structure, that accepts a function as an argument, to be infected with templates, and instead be a plain function/structure. Thanks to that:
* there will be only one instance of it;
* you'll be able to export such a function in public interface of a dynamic library without exposing its source code in a header file;
* compilation will be faster, because the compiler won't have to instantiate N different versions of a template;
* you'll see fewer scary template errors.
Your life as a human being will be happier.
Of course there are better alternatives to std::function in the wild, but the general idea remains.
std::invokable will just make working with templates nicer. But not working with templates is nicer still.
EDIT: You can think of std::function as the LSP inside C++. There is a famous matrix where rows are for editors, and columns are for languages. In case of std::function rows are functions accepting functions as arguments, and columns are functions passed as function arguments. The matrix represents template instantiations, when you use templates. By using std::function you avoid the matrix and instead have just two vectors of length N and M, if the matrix was NxM (N functions accepting functions as arguments, M functions passed as arguments).
* there will be only one instance of it;
* you'll be able to export such a function in public interface of a dynamic library without exposing its source code in a header file;
* compilation will be faster, because the compiler won't have to instantiate N different versions of a template;
* you'll see fewer scary template errors.
Your life as a human being will be happier.
Of course there are better alternatives to std::function in the wild, but the general idea remains.
std::invokable will just make working with templates nicer. But not working with templates is nicer still.
EDIT: You can think of std::function as the LSP inside C++. There is a famous matrix where rows are for editors, and columns are for languages. In case of std::function rows are functions accepting functions as arguments, and columns are functions passed as function arguments. The matrix represents template instantiations, when you use templates. By using std::function you avoid the matrix and instead have just two vectors of length N and M, if the matrix was NxM (N functions accepting functions as arguments, M functions passed as arguments).