Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411512 Posts in 69376 Topics- by 58430 Members - Latest Member: Jesse Webb

April 26, 2024, 04:47:49 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)What modern features you want to see in C++?
Pages: 1 [2] 3 4
Print
Author Topic: What modern features you want to see in C++?  (Read 6242 times)
fluffrabbit
Guest
« Reply #20 on: October 10, 2019, 12:57:42 PM »

C seems to be a lot safer, but this is a C++ thread and the STL brings the bulk of C++'s convenience. Convenience, after all, is why it was used for the JSF project before MISRA C++ existed.

I read in an article I linked earlier that allocating std::vector and similar STL things in advance gives you warm fuzzies. That's technically a dynamic heap allocation as it happens once the program is running, but if it happens during program initialization, who cares? You can reserve your vectors during init and you're safe. Right? Of course, no C++ compiler is going to warn you if you're not safe.
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #21 on: October 10, 2019, 03:16:38 PM »

@qMopey, what do you think about std::array? It's simply a well known C array, wrapped in a small thing that adds the length as compile time constant. Keeping all the information about the array together, instead of having the classic mistake of using the wrong size and going out of bounds.

I think the idea is good, and the need for an array type is an important feature missing from C/C++.

If I poke into the header as implemented by MSVC I see some things that I don't like, meaning I don't like how it's implemented. Which is a different topic than whether or not something like std::array is needed or could be good (of course it's needed and could be good!).

Code:
// At the top off <array> header ...
#include <algorithm>
#include <iterator>
#include <tuple>

These are pretty big headers that pull in other pretty big headers, that pull in other pretty big headers. This implementation is quite objectively terrible for this reason alone. All that code, and for what? An array that could be implemented sufficiently in 10 lines of portable C++.

Another qualm I have is how the size of the array is templated and the memory for the array is stored directly inside the array itself. Personally if I were to implement a templated array I would do it more like this:

Code:
template <typename T>
struct Array
{
    size_t capacity;
    T* data;
};

And that's about it. Maybe some convenience member functions for things like array.begin() and array.end(), but I wouldn't even implement iterators. Yes, STL has lots of correctly implemented and useful generic algorithms like sorting and whatnot, which make use of iterators, but even so there's nothing wrong with a pointer. array.data + i works just fine, and generates less templated code compared to iterator implementations (much lower compile times).



Then there's the question of "what about `Array` vs `ArrayView`", where Array would be like std::array and store elements directly, and ArrayView would be like my example of just a pointer to the underlying data. My argument would be to only have Array, and no by-value semantics at all. The example I wrote above can handle all use cases just fine. Storing elements directly in the array is, in my opinion, more expressive than necessary.

In my own game I use an array, but it's actually just a stripped down version of std::vector. The API looks like this.

Code:
template <typename T>
struct array
{
    array();
    explicit array(void* user_allocator_context);
    explicit array(int capacity, void* user_allocator_context);
    ~array();

    T& add();
    T& add(const T& item);
    T& insert(int index);
    T& insert(int index, const T& item);
    void set(int index, const T& item);
    void remove(int index);
    T& pop();
    void unordered_remove(int index);
    void clear();
    void ensure_capacity(int num_elements);
    void steal_from(array<T>* steal_from_me);

    int capacity() const;
    int count() const;

    T& operator[](int index);
    const T& operator[](int index) const;

    T* operator+(int index);
    const T* operator+(int index) const;

    array<T>& operator=(array<T>& rhs);

    T& last();
    const T& last() const;

    T* data();
    const T* data() const;

private:
    int m_capacity = 0;
    int m_count = 0;
    T* m_items = NULL;
    void* m_mem_ctx = NULL;
};

No need for iterators. Is compatible with std::sort. The only thing it can't do, that I sometimes wish it could, is to refer to memory elsewhere without attempting to call `free` upon destruction (like the above `Array` example). But, I never really run into this use-case in practice.
« Last Edit: October 11, 2019, 11:29:17 AM by qMopey » Logged
fluffrabbit
Guest
« Reply #22 on: October 10, 2019, 03:38:24 PM »

Earlier this week I had to replace the vector implementation in the pathfinding library I'm using with std::vector. The custom implementation made the program crash constantly; std::vector works fine. So there's one for the STL.
Logged
InfiniteStateMachine
Level 10
*****



View Profile
« Reply #23 on: October 10, 2019, 06:07:44 PM »

I'm just not sure c++ is the language I'd want to be used for mission critical stuff due to a lot of concerns already talked about in this thread.
Got bad news for you. It's used for a lot of mission critical stuff, including medical devices. But generally, that is a limited subset.

In embedded realtime systems, there is one simple rule that rules out a lot of the complexity of C++. No dynamic memory, allocations happen on startup and that's it.


I'm well aware and always a little nervous when flying :D
Logged

InfiniteStateMachine
Level 10
*****



View Profile
« Reply #24 on: October 10, 2019, 06:10:41 PM »

As for delegates. It's a similar argument. It really depends what I'm comparing them to. Ideally I prefer a language that doesn't make the distinction between a value and a function.

I agree. A big problem with C++ is how complicated multiple inheritance can make things. A delegate-like thing in C language could be extremely simple.

Code:
struct var_t
{
    void* data;
    type_t* type;
    union_t value;
};

struct delegate_t
{
    var_t context;
    void (*fn)();
};

var_t delegate_call(delegate_t* delegate, int param_count, var_t params[8]);

Ultimately all that's needed is a single function pointer to store the address of the function to call, some type information represented by pointers to type structs (the type structs could be, for example, stored in a static table), and that's about it.

The `data` pointer points to the actual referenced value in memory. It can be anywhere; on the heap, on the stack, or in the `value` member. `value` is just a union for common types, like int/float, to pass parameters by value. `context` is like the `this` pointer. It's the first parameter to the called function, unless the `context` var is empty, then it is not used.

This is actually very similar to how my own ECS works for my game. I made a very similar system for calling system functions with type tables and function pointers.

In C++ it becomes very complicated to store a function pointer, since the size of the function pointer is implementation dependent and can vary quite wildly. std::function simply allocates the function pointer on the heap... Which is dumb in my opinion. All this complexity is pointless.

Working in games. There's only one company I've been allowed to use std::function. Almost every company I worked at had the same fastdelegate.h file in their codebase. That file is almost legendary to me at this point haha.

Logged

qMopey
Level 6
*


View Profile WWW
« Reply #25 on: October 10, 2019, 10:13:30 PM »

I've seen that fastdelegate.h file a few times as well! It's really a shame C++ templates are required in such complicated ways to get something working that is really supposed to be simple. There's no intrinsic need for such complexity. It's all a byproduct of poor language support and poor anticipation of common use-cases and production needs.

I've written my own version based on my friend John Edward's talk (but have instead deferred to a simpler and more restricted "C-style" version):

-- I'm actually the person who uploaded this to youtube Smiley
Logged
Schrompf
Level 9
****

C++ professional, game dev sparetime


View Profile WWW
« Reply #26 on: October 10, 2019, 10:30:06 PM »

Wow, this is so far off the truth I have to intervene.

In C++ it becomes very complicated to store a function pointer, since the size of the function pointer is implementation dependent and can vary quite wildly.

No. C++ function pointers are exactly as large as C function pointers, and your little "does only one thing and blames others for providing more" would exactly work in C++ as it does in C.

Quote from: qMopey
Another qualm I have is how the size of the array is templated and the memory for the array is stored directly inside the array itself. Personally if I were to implement a templated array I would do it more like this:

No. You're doing a statically sized vector here, that's not the purpose of an std::array. The later is explicitly designed to use static storage. You don't want that? Yeah, fine, don't use it. But don't compare it to your low-fi std::vector, and don't blame people for solving problems you're not even aware of.

std::function could use a small block optimization, though.
Logged

Snake World, multiplayer worm eats stuff and grows DevLog
qMopey
Level 6
*


View Profile WWW
« Reply #27 on: October 11, 2019, 11:17:35 AM »

No. C++ function pointers are exactly as large as C function pointers, and your little "does only one thing and blames others for providing more" would exactly work in C++ as it does in C.

I also meant member function pointers to be included as "function pointers", which is why my prior example had a `context` pointer and went into detail to mention how it relates to the `this` pointer and multiple inheritance. It gets quite complex when implementing a functor that can handle any kind of member function. Example article.

And yeah my example works in C++ the same as C by design. I'm criticizing the unnecessary complexity we see in situations like the above article. There's nothing wrong with that criticism, and it isn't "blaming people". There are no unnamed people being marginalized here; we're just discussing the merits of code + language.

But don't compare it to your low-fi std::vector, and don't blame people for solving problems you're not even aware of.

Why so serious? It's just an opinion  Shrug

The reason I mentioned my vector was to talk about iterators. There was a topic shift. I was only comparing std::array to the tiny templated array struct (copy pasted below).

Code:
template <typename T>
struct Array
{
    size_t capacity;
    T* data;
};

My apologies if the topic shift was confusing. I added a horizontal rule to my old post - hopefully that helps separate the two topics more clearly.
« Last Edit: October 11, 2019, 11:29:47 AM by qMopey » Logged
Daid
Level 3
***



View Profile
« Reply #28 on: October 11, 2019, 11:54:41 AM »

Actually, a std::function can store a lambda, which can capture data next to just the function pointer. And is pretty much amazing.
So while it has some overhead in size and runtime. It is great for callbacks from things like UI. A large goal is just removing boilerplate code.

Try doing this in C, and see how much glue code you'll be adding and how easy it is to leak memory.
Code:
std::vector<std::function<void()>> funcs;

for(int n=0; n<10; n++)
{
    std::string str = "first:" + std::to_string(n);
    funcs.emplace_back([str]()
    {
        std::cout << str;
    });
}
for(int n=0; n<10; n++)
{
    std::string str = "second:" + std::to_string(n);
    funcs.emplace_back([str]()
    {
        std::cout << str;
    });
}

for(auto f : funcs)
    f();


@qMopey: microsofts STL is generally not the best place to look at. Hell, microsofts code examples are generally bad things to look at. For example, they have a tendency to just cast parameters to the proper type, even if they are incompatible like wrong function pointers.

Also, as of C++11, there is move semantics, allowing you to "steal" buffers from objects like the std::vector. But if you want to refer from multiple objects to a single one and don't free it until everyone is done with it, you want std::shared_ptr.
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
buto
Level 0
***



View Profile WWW
« Reply #29 on: October 11, 2019, 01:45:52 PM »

Just wanted to say: I think move semantics are wonderful. They allow one to code RAII and still allow to minimize memory allocations without resorting to shared_ptr everywhere. Also they allow concepts like unique_ptr, which is great.

Also since C++11: return by value with copy elision:
Code:
std::vector <int> getSomeInts (size_t const n)
{
  std::vector <int> someInts (n);
  std::iota (someInts.begin (), someInts.end (), 0);
  return someInts;
}

Only one dynamic allocation is done. No unique or shared pointers, no overhead, no leaking memory...

I sometimes could use optional references and it would be nice to be able to get the largest value in an enum or enum class (yeah - not that fancy...)
Logged

qMopey
Level 6
*


View Profile WWW
« Reply #30 on: October 11, 2019, 01:48:14 PM »

I totally agree that lambda captures are pretty darn useful. Callback based C code gets really messy really quick.
Logged
fluffrabbit
Guest
« Reply #31 on: October 11, 2019, 02:33:46 PM »

This may be somewhat philosophical, but accepting a void* for a callback function is no more complex than JavaScript's event handling. Nested functions get messy quick. I do use them with <algorithm> and in places where it's convenient, but it's a constant reminder that C++ is not a clean-looking language.
Logged
Daid
Level 3
***



View Profile
« Reply #32 on: October 11, 2019, 10:55:14 PM »

This may be somewhat philosophical, but accepting a void* for a callback function is no more complex than JavaScript's event handling. Nested functions get messy quick. I do use them with <algorithm> and in places where it's convenient, but it's a constant reminder that C++ is not a clean-looking language.
void* is not easier if you pass complex data structures. As who can properly clean those up?

Only one dynamic allocation is done. No unique or shared pointers, no overhead, no leaking memory...
While move symantics are wonderful. There is always a cost of abstraction.
I do not think there is zero overhead to a pure c function in this case.


Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
fluffrabbit
Guest
« Reply #33 on: October 12, 2019, 11:28:56 AM »

You wouldn't pass a complex data structure as a void*, you'd pass it as a struct or pointer thereto, unless I'm missing something.
Logged
Daid
Level 3
***



View Profile
« Reply #34 on: October 12, 2019, 12:35:03 PM »

You wouldn't pass a complex data structure as a void*, you'd pass it as a struct or pointer thereto, unless I'm missing something.
Yes, but if that pointer points to a complex data structure, what then, who owns that memory? Who frees it?
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
qMopey
Level 6
*


View Profile WWW
« Reply #35 on: October 12, 2019, 12:42:02 PM »

You wouldn't pass a complex data structure as a void*, you'd pass it as a struct or pointer thereto, unless I'm missing something.
Yes, but if that pointer points to a complex data structure, what then, who owns that memory? Who frees it?

Generally I have found freeing resources to not be a big problem, even in very complicated/high performance scenarios. Usually the difficulty I have seen is an explosion of tiny callback functions that start to completely dismantle code flow. Personally I've never found RAII to be a useful concept, even in this kind of scenario.
Logged
fluffrabbit
Guest
« Reply #36 on: October 12, 2019, 12:57:34 PM »

So let's say you have a struct that goes through metamorphosis as a chunk of void* memory. Just bytes. You cast it back into a struct. If you know for certain that the first byte is the start of the struct and the data is uncorrupted, you can free the heap elements of the struct.

Let's say you get the data from a file. Hopefully your struct is POD. If not, forget the file part. Your constructors and destructors will depend on the structure. C++ doesn't add much useful stuff here except for std::vector, std::any, and other dynamic STL types. But if you're avoiding the STL, I don't see any fundamental difference.
Logged
powly
Level 4
****



View Profile WWW
« Reply #37 on: October 15, 2019, 04:47:32 AM »

So let's say you have a struct that goes through metamorphosis as a chunk of void* memory. Just bytes. You cast it back into a struct. If you know for certain that the first byte is the start of the struct and the data is uncorrupted, you can free the heap elements of the struct.
I believe the point is: if that struct contains a pointer, who frees the pointed memory?

Personally I like C++ exactly because it lets you code both higher and lower level; you get both nice convenience features for setup/pipeline code and can optimize the inner loops to your hearts content.
Logged
fluffrabbit
Guest
« Reply #38 on: October 15, 2019, 05:01:07 AM »

In C, isn't the convention to have manual destructors? FreeThing() would free a Thing, including all hierarchically nested data, regardless of who calls it.

C++ does get high and low, but so does D, and D has a nicer syntax. Also don't forget Rust, if you're into that (I'm not).
Logged
Daid
Level 3
***



View Profile
« Reply #39 on: October 15, 2019, 06:11:31 AM »

In C, isn't the convention to have manual destructors? FreeThing() would free a Thing, including all hierarchically nested data, regardless of who calls it.
The question remains, who calls it? If the memory is owned by who does the callback, then that module also needs to call this FreeThing function. And thus, it needs to know about it, and thus, another thing you need to pass and store yourself.

Just about nothing C++ does cannot be done manually with C. But the keyword here is manually.



But the most important thing that will come in C++20 is: THE RETURN OF PI! instead of M_PI being a compiler specific extension.
https://en.cppreference.com/w/cpp/numeric/constants see "pi_v"
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
Pages: 1 [2] 3 4
Print
Jump to:  

Theme orange-lt created by panic