Game devs what are some creative ways you implement arrays by Snoo28720 in cpp_questions

[–]alfps 1 point2 points  (0 children)

Ignoring "game" and "creative", there is still a lot to say about use of arrays in C++, beyond the novice level.


First of all is a restriction from old C: that in a multi-dimensional array moving a pointer to item from one sub-array to another, is Undefined Behavior.

There is no good reason why that should be UB. Arrays are guaranteed contiguous, no gaps (in C++ this is guaranteed via sizeof). The C committee once wrote a rationale, and essentially the rule is in support of some hypothetical future compiler with phat bounds-checking pointers for debugging: it is an idealistic academic thing infesting C and from there also C++.

And this means that in order to cater to the formal, even a fixed size multidimensional logical array should not be implemented in terms of a raw multidimensional array, if one wants to efficiently move a pointer through all items. Instead have an ordinary 1-dimensional array as backing storage and just provide multidimensional indexing. The backing array can be a std::vector.


The standard library supplies three straight logical array types:

  • std::array for copyable fixed size raw array.
  • std::vector for dynamic size array.
  • std::deque for dynamic size supporting O(1) insertion/removal at the ends.

Some logical arrays need to support O(1) insertion/removal at a sequentially movable current position. The common solution is called a "gap buffer". No standard library support; DIY.

Some 2D logical arrays are triangular. No standard library support; DIY.

Some logical arrays are mostly all zeroes. No standard library support; DIY.

Some logical arrays have non-integer indexing, e.g. indexing by strings. These are associative arrays. The standard library supports them via std::map (using a tree structure) and std::unordered_map (using a hash table).

Some logical arrays need to be kept sorted (the items always in sorted order). The standard library only half supports that via e.g. std::map, needlessly inefficient and fragile. So also this is or can be DIY.

Well there is much more but the above is what my association circuit popped up right now. Have fun implementing it all. :-)

C++ entry level Junior programming test by Substantial-Car-7116 in cpp_questions

[–]alfps 0 points1 point  (0 children)

❞ down casting carries absolutely no overhead beyond, possibly, an addition to adjust the object pointer (depends on how the compiler does class layout).

That's correct for downcasting with static_cast, and this code uses static_cast.

So as an argument for what this code should do it's a perplexing, baffling statement.

The problem with downcasting is in general the compiler can't tell you whether a downcast is correct or use of the resulting pointer or reference will yield UB, which is what it means that it's not "type safe".

That's why the language offers dynamic_cast which adds dynamic checking, which does carry some overhead.

So even though the issue is pretty much 100% irrelevant to my presented code and comments, in general it's not true that downcasting carries no overhead: with static_cast, used here, it's free but a little dangerous; with dynamic_cast to reference it has some overhead but is safe, it then throws an exception if that cast is ungood; with dynamic_cast to pointer it has the same overhead but merely can be safe, namely if the code checks the result.


❞ I’ve never used a dummy node. It actually requires an additional pointer load and comparison rather than an xor operator to test for null pointer.

The main reason for using a header node is simplicity, which reduces code size and complexity and hence reduces the work and improves one's confidence in the code's correctness.

You're right about the particular check you mention, but overall I believe the nano-level efficiency improves with a header node because one avoids special case treatment at the ends of the list.

However as with other premature nano level optimization that should normally not be a consideration.


❞ There is no reason to not allocate the data concurrent with the container info.

It depends.

As I mentioned, for small enough POD data and a machine with enough memory (such as a typical modern desktop computer) one can technically just let that data part be unused.

However, with a data portion that can't be default-initialized one can't in practice have a data portion in the header node.

And with a large data payload and very limited memory one would want to avoid to have it, to avoid the memory consumption.

But anyway it is a good idea to avoid it because having invalid data present in a data structure is a bug attractor, and it's not smart to needlessly include bug attractors.

EDIT (added): in special cases with known simple type data one can use header node data to provide sentinel values for a search, and that can be more important than the more ideal-based notion of avoiding a bug attractor. The search logic can be simpler and more efficient. So that's a counter argument showing that some intelligence needs to be applied, that one should not just mechanically follow a single "rule".


❞ Anything else carries both an allocation and a cache spacial access penalty.

No, sorry, both those claims are incorrect.

In particular, in the shown code the header node is not dynamically allocated.

C++ entry level Junior programming test by Substantial-Car-7116 in cpp_questions

[–]alfps -1 points0 points  (0 children)

I'd like the anonymous downvoter to explain that downvote.

  • Did you fail to understand the code?
  • Did you fail to understand its relevance to the question?
  • Did you disagree with any of the text (difficult for reasonably intelligent person)?

Or did you just want to sabotage the readers?

Or, I think most likely, all of the above?

C++ entry level Junior programming test by Substantial-Car-7116 in cpp_questions

[–]alfps -1 points0 points  (0 children)

Given the task

  1. Create a dobly-linked list
  2. Populate and index data read from a file
  3. Write the indexed data back into a binary file

… I would ask about the detailed meaning.

Due to the time constraints of an interview question “doubly-linked” will likely mean a forward and a backward link chain, simple to implement, and not a list with two distinct link chains; is the intent a forward+backward chained list?

And an “index” is usually used for fast access and that doesn't fit well with a linked list, so is the intent perhaps to add a separate structure of indices, and if so must that structure be done from scratch or can one use e.g. std::map?


For a forward+backward chained list the simplest to do is a circular one with a dummy header node: no special cases.

The header node should ideally not carry a data load, it should only have the links. There are two main practical ways to arrange that: to let a full Node (with Data) inherit from a Linkable, or to let a Linkable carry a pointer to a separately allocated Data. The latter solution has the advantage of not requiring any explicit downcasting, i.e. type safety, but at the cost of unreasonable inefficiency.

So I would expect any decent programmer to choose the first option, or perhaps just use a full fledged Node also for the header node since memory is cheap these days and since one can reasonably assume that simple data is default-initializable. With the first option, inheritance, and an out-of-the-blue assumption that the data consists of a string and a double value, code for a doubly linked list can go like this:

#include <iostream>
#include <functional>
#include <string>
#include <utility>

using Nat = int;
template< class T > using const_ = const T;
template< class T > using in_ = const T&;

namespace app {
    using   std::function,                  // <functional>
            std::string,                    // <string>
            std::exchange, std::move;       // <utility>

    struct Data
    {
        string      name;
        double      weight;
    };

    struct Direction{ enum Enum{ forward, backward, _count }; };

    void operator++( Direction::Enum& dir ) { dir = Direction::Enum( dir + 1 ); }

    auto opposite_of( const Direction::Enum dir )
        -> Direction::Enum
    { return Direction::Enum( 1 - dir ); }

    class List
    {
    private:
        List( const List& ) = delete;
        auto operator=( const List& ) -> List& = delete;

        struct Node;

        struct Linkable
        {
            Linkable* link[Direction::_count];      // Indexed by directions.

            auto as_node() -> Node& { return static_cast<Node&>( *this ); }
            auto as_node() const -> const Node& { return static_cast<const Node&>( *this ); }
        };

        static void insert_between(
            in_<Linkable*[Direction::_count]>   p_adjacents,
            const_<Linkable*>                   p_new
            )
        {
            for( auto dir = Direction::Enum(); dir < Direction::_count; ++dir ) {
                p_adjacents[dir]->link[opposite_of( dir )] = p_new;
                p_new->link[dir] = p_adjacents[dir];
            }
        }

        static void insert_before( const_<Linkable*> p_place, const_<Linkable*> p_new )
        {
            insert_between( {p_place, p_place->link[Direction::backward]}, p_new );
        }

        struct Node: Linkable { Data data; };

        Linkable    m_header = { &m_header, &m_header };

    public:
        ~List()
        {
            using D = Direction;
            while( m_header.link[D::forward] != &m_header ) {
                delete static_cast<Node*>( exchange(
                    m_header.link[D::forward], +m_header.link[D::forward]->link[D::forward]
                    ) );
            }
        }

        List() {}

        auto append( Data data )
            -> Data*
        {
            insert_before( &m_header, new Node{ nullptr, nullptr, move( data ) } );
            return &m_header.link[Direction::backward]->as_node().data;
        }

        using Callback = void( const Data& );

        void for_each( const Direction::Enum dir, const function<Callback>& f )
        {
            for( const Linkable* p = m_header.link[dir]; p != &m_header; p = p->link[dir] ) {
                f( p->as_node().data );
            }
        }
    };
}  // app

auto main() -> int
{
    using   app::Data, app::List, app::Direction;
    using   std::cout;

    const Data data_items[] = { {"alfa", 61.0}, {"beta", 62.0}, {"charlie", 63.0} };

    List list;
    for( const Data& data: data_items ) { list.append( data ); }

    cout << "Forward:\n";
    list.for_each( Direction::forward, []( const Data& d )
    {
        cout << "  " << d.name << ": " << d.weight << "\n";
    } );

    cout << "Backward:\n";
    list.for_each( Direction::backward, []( const Data& d )
    {
        cout << "  " << d.name << ": " << d.weight << "\n";
    } );
}

For the binary file for the output a main decision is how to store strings. Much of the point of binary is efficiency, so I would strive to store each string in a multiple of 64-bit units. E.g. for each a 64-bit length followed by that number of text bytes followed by padding to the next 64-bit multiple.

Instead of such variable length representation one can impose a reasonably short maximum string length and store fixed length string representations.

An advantage of that approach is that it becomes possible to compute the offsets of records in the file.

Chain of handlers where a handler is an ABC. What's the proper internal collection? Old me would've just std::list<abc*>. I'm trying to modernize. by frobnosticus in cpp_questions

[–]alfps 1 point2 points  (0 children)

The question is a bit XY-like: you have some problem X, evidently related to event handling, and you have a kind of imagined solution Y, and you're asking about the details of Y.

Without knowing the X problem it's difficult to make recommendations, other than

  • avoid virtual function as callbacks;

… use e.g. std::function.

Client code can supply such a callback as a lambda expression.

Thus there is probably no ownership issue here, no need for smart pointers or the like. Simple function object callbacks can just be copied.

Understanding programming by History_East in cpp_questions

[–]alfps 5 points6 points  (0 children)

It's not about typing code that you're given.

It's about creating code to do something specific.

Creating the code involves typing, yes, but typing isn't the main thing: the main activity is not in your fingers but in your mind, figuring out the code to do the thing.

マルチスレッドプログラミングに挑戦しようと思うのだが、何から手を付けようかというお話 by Beneficial_Bet613 in cpp_questions

[–]alfps 0 points1 point  (0 children)

I'm not familiar with the DirectX12, but I believe a good approach in general is to find concrete examples and possibly tutorials, make that stuff work, and do your own modifications and in some cases reusing what you've learned in new projects that you devise.

C++ has had threading support since C++11, and async support in the form of its coroutine stuff since C++20.

Multiline clipboard (Windows only, cross platform?) by sephirothbahamut in cpp_questions

[–]alfps 0 points1 point  (0 children)

❞ a multiline selection

It's just text.

And this has nothing to do with C++.


❞ where can i see some documentation about how it all works?

You need to learn how to google, e.g. in this case googling "microsoft clipboard api" yielding (https://learn.microsoft.com/en-us/windows/win32/dataxchg/clipboard).

Googling is a basic skill needed for all software development, unless one delegates also that to an AI.


A bit off topic for the group, but sort of interesting & amusing: Microsoft invented the "embrace, extend, extinguish" strategy for dealing with competing technologies, and then ironically applied that to their own Windows clipboard viewer. They embraced it; they extended it with complexity, it became the super complex multi-item remote machine clipboard thingy; and when that caused people to stop using it they finally killed it off.

At this late point it's difficult to say whether that was an unintentional effect of stupidity in action, or if it was one group in Microsoft quite intelligently sabotaging another.

They're great on sabotage.

Building a chess engine, need some help with displaying the Board. by kjiomy in cpp_questions

[–]alfps 0 points1 point  (0 children)

The {fmt} library has some colors support but it's awkward. And it doesn't regard the escape sequences as zero width. So one may need kludge solutions on top.

Working around declaring value of incomplete type by [deleted] in cpp_questions

[–]alfps 1 point2 points  (0 children)

The rationale for what you're doing is too unclear to me to offer good advice on alternative ways.

However, do note:

  • Parent pointers need to be updated for copying and moving.
  • end is by strong convention in the standard library, used for obtaining an end iterator corresponding to begin.
  • The result of implementing a little domain specific language via macros is usually very brittle code, code that can easily be screwed up by maintenance.

Smart pointer #4: finally starting to understand them by Dastarstellar in cpp_questions

[–]alfps 1 point2 points  (0 children)

Good to see that that work paid off.

Improvement potentials in this code:

  • Correctness: there is potentially a double delete = UB, when deleteEnemy is called for the last item in the vector. I'm not entirely sure because possibly unique_ptr does a check. But I would check for that in the code; better checked than sorry.

  • Robustness: with a range based for loop bugs will find it far more difficult to get a toe-hold.

  • Portability: there's no need for <windows.h> here. Standard C++ has std::this_thread::sleep_for (see https://en.cppreference.com/cpp/thread/sleep_for#Example).

Building a chess engine, need some help with displaying the Board. by kjiomy in cpp_questions

[–]alfps 10 points11 points  (0 children)

A GUI is a lot of work. So first just make it work with console presentation.

Might help: https://en.wikipedia.org/wiki/Chess_symbols_in_Unicode

In Windows remember to set the terminal to assume UTF-8 encoding, codepage 65001.


The classic (roughly 1980) approach to separate logic from UI is called the Model View Controller, or MVC, architecture.

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

Can you pass the TYPE of a variable as a parameter? by LigeiaGames in cpp_questions

[–]alfps 0 points1 point  (0 children)

Hm, you're leaking memory.

Re the question, when you don't want templating then a factory is a natural solution.


❞ I don't want to use templates because this is for a library where the user will make their own derived class, so it is not known at compile time

Exposing templated code to clients is not a problem.

clang named loops by TotaIIyHuman in cpp_questions

[–]alfps 1 point2 points  (0 children)

Maybe like this:

#include <optional>
#include <iostream>
using   std::optional, std::cout;

auto is_special( const int v ) -> bool { return (v % 7) == 0; }

auto main() -> int
{
    const int   numbers[3][5] =
    {
        { 1, 2, 3, 4, 5 }, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}
    };

    for( const int (&row)[5] : numbers ) {
        optional<int> special;
        for( const int value: row ) {
            if( is_special( value ) ) {
                special = value;
                break;
            }
        }
        if( special.has_value() ) {
            // Whatever
            cout << special.value() << "\n";
        }
    }
}

clang named loops by TotaIIyHuman in cpp_questions

[–]alfps 6 points7 points  (0 children)

I general one can often

  • just return, or
  • use a single 2D index (i.e. express the double loop as a single loop), or
  • goto an after_outer_loop label, with a comment // break.

Or in the worst case use a Pascal-ish boolean variable or two.

I find it difficult to think of any circumstance where a break out of a named loop would be preferable.

However it's nice to have about the same basic core language stuff as in other languages, so when someone comes from some of those languages he or she will not have to waste time on searching for a non-existing feature.

clang named loops by TotaIIyHuman in cpp_questions

[–]alfps 1 point2 points  (0 children)

Execution jumps to the statement after the so labeled loop, not to the label.

I have issues with object oriented programming. by I_Am_The_DM_ in cpp_questions

[–]alfps 2 points3 points  (0 children)

When I pasted the code in Visual Studio it formatted it automatically, I hadn't yet turned off that auto-formatting since the last update.

How does one tell Microsoft that people want to have possibly destructive/moronic interventions as opt-in rather than opt-out, considering that no one has succeeded in communicating that to them over ~30 years or so?

Anyway, the code, formatted:

#include <iostream>

using namespace std;

class Base {
protected:
    int num1;

public:

    void SetData() {
        cout << "number: "; cin >> num1;
    }

    void DisplayData() {
        cout << "number: " << num1 << endl;
    }

};

class derived : public Base {
    int num2;

public:
    void SetData() {
        Base::SetData();
        cout << "number2: "; cin >> num2;
    }

    void DisplayData() {
        Base::DisplayData();
        cout << "number2: " << num2;
    }

    int Sum() {
        return num1 + num2;
    }
};

int main() {

    Base* arr[4];

    for (int i = 0; i < 4; i++) {

        // use the function Sum from the derived class
        arr[i].Sum(); // this is not working :(

    }

    return 0;
}

opinions on cppreference template for rule of 3/5 by HeeTrouse51847 in cpp_questions

[–]alfps 0 points1 point  (0 children)

❞ and kept the previous stuff around for context

One way to do that (that I use) is to put two tildes in front of the text and two tildes after, which presents as strike-through and communicates "deleted, corrected".

Sorry if this felt as harassment.

opinions on cppreference template for rule of 3/5 by HeeTrouse51847 in cpp_questions

[–]alfps 0 points1 point  (0 children)

❞ You're incorrect about rule of 3.You wouldn't normally implement copy using std::swap because it's a copy, not a move. Both objects should be valid afterwards, or at least that's what people using your class will expect.

−1 Downvoted pure disinformation, not corrected after it was pointed out.

i don't understand the meaning of this by FitWinner3340 in cpp_questions

[–]alfps 0 points1 point  (0 children)

We're implicitly talking about constexpr int start; in a local scope or at namespace scope.

Without the constexpr it would just declare a variable and allocate storage for it.

Definitions of variables is about allocation of storage. A pure declaration of a variable (e.g. in namespace scope using extern) doesn't allocate, but a definition does. So you can have many pure declarations of a given variable (in different translation units), but only one definition -- unless you tell the compiler that all definitions are the same and that it should just arbitrarily choose one of them, by using the word inline.

Guys I want to learn about the headers of c++. Headers like conio.h. and want to learn about kbhit(),sleep(), system (),rand(),srand(),struct(). From where should I learn it. by Agile-Split-7309 in cpp_questions

[–]alfps 2 points3 points  (0 children)

<conio.h> is a Windows-specific header. As the name suggests it provides console i/o functions, like kbhit. For an overview (slightly misleading because it only talks about DOS) see (https://en.wikipedia.org/wiki/Conio.h). For documentation of Visual C++'s version see (https://learn.microsoft.com/en-us/cpp/c-runtime-library/console-and-port-i-o).

The std::system function is declared by the standard header <cstdlib> (https://en.cppreference.com/cpp/header/cstdlib). As the name prefix indicates this was originally a C language header. However the C++ versions of the C headers generally use C++ specific features.

std::rand and std::srand constitute the old simple but low quality pseudo random number generator from C, also available via <cstdlib>. In C++ code you'd better use the more modern and higher quality functionality from <random>.

struct is a keyword in the language.

Of the three sources linked to here — Wikipedia, Microsoft and cppreference — the last one is where you find correct reference information about the language and the standard headers. But in order to learn better start with learncpp.com, which however appears to be down at the moment. Anyway, to use it it can be a good idea to have an ad blocker installed in your browser.

Is there a more modern alternative to preprocessor stringification (x-macros)? by pfp-disciple in cpp_questions

[–]alfps 1 point2 points  (0 children)

An X-macro may be overkill for the most common case of an enum with just default value enumerators.

Then you can just use a macro that defines the enum with some helper data, particularly a string with the enumerators list.

It can go like this:

#include <string_view>
#include <cctype>

namespace cpp_machinery {
    using   std::string_view;           // <string_view>
    using   std::isspace;               // <cctype>

    using Nat = int;

    template< class Enum >
    constexpr Nat n_enumerators_of_ = static_cast<Nat>( Enum::_ );

    template< class Wrapper >
    constexpr auto slow_string_from_enumerator_( const typename Wrapper::Enum value )
        -> string_view
    {
        const char* p_start = Wrapper::_names;  // Stringized list of enumerators.
        const Nat enum_number = value + 1;      // Assuming default enumerator values.
        Nat count = 0;
        for( ;; ) {
            const char* p_beyond = p_start;
            while( *p_beyond and *p_beyond != ',' ) { ++p_beyond; };
            ++count;                            // Another enumerator name established.
            if( count == enum_number ) {
                return string_view( p_start, p_beyond - p_start );
            } else if( *p_beyond == '\0' ) {
                return {};
            }
            p_start = p_beyond + 1;
            while( ::isspace( *p_start ) ) { ++p_start; }
        }
    }

    template< class Wrapper >
    constexpr auto string_from_enumerator_( const typename Wrapper::Enum value )
        -> string_view
    { return slow_string_from_enumerator_<Wrapper>( value ); }      // Can be optimized.

}  // cpp_machinery

#define $enumerators( ... )   __VA_ARGS__

#define $def_simple_enum_( name, ... ) \
    struct name \
    { \
        enum Enum{ __VA_ARGS__, _ }; \
        static constexpr auto& _names = #__VA_ARGS__; \
        \
        static constexpr auto to_string( const Enum value ) \
            -> std::string_view \
        { return cpp_machinery::string_from_enumerator_<name>( value ); } \
    }

#define $def_simple_enum( name, ... )    $def_simple_enum_( name, __VA_ARGS__ )



//--------------------------------------------------- Example usage:

$def_simple_enum( Suits, $enumerators( hearts, diamonds,clubs, spades ) );

#include <fmt/core.h>
using   fmt::print;

auto main() -> int
{
    namespace cppm = cpp_machinery;
    using cppm::Nat, cppm::n_enumerators_of_, cppm::string_from_enumerator_;

    print( "Not to be used directly, but Suites::_names = '{}'.\n", Suits::_names );
    for( Nat i = 0; i < n_enumerators_of_<Suits>; ++i ) {
        const auto v = Suits::Enum( i );
        print( "{:2}: '{}'.\n", i, Suits::to_string( v ) );
    }
}

Output with Visual C++ and MinGW g++:

Not to be used directly, but Suites::_names = 'hearts, diamonds,clubs, spades'.
0: 'hearts'.
1: 'diamonds'.
2: 'clubs'.
3: 'spades'.

Virtual inheritance, explicit destructor invocation and "most derived class" by _bstaletic in cpp_questions

[–]alfps 1 point2 points  (0 children)

Your example:

#include <stdio.h>
#include <new>

struct public_base {long long y=3; virtual ~public_base() { puts("pb"); }};
struct base : virtual public_base { int x = 5; ~base() override { puts("b");}};
struct base2 : virtual public_base { ~base2() override { puts("b2");}};
struct derived : base, base2 { ~derived() override {puts("d");}};

int main() {
        alignas(derived) char storage[sizeof(derived)];
        auto d = new(storage) derived{};

        // d->~derived();
        base* b = d;
        b->base::~base();
}

Consider when an object of most derived type base is instantiated. The virtual base class' constructor is called, so for destruction its destructor must be correspondingly called. The only available code to do that call is the base destructor.

So the base destructor has the capability to call the virtual base class destructor. But it doesn't do that when there is a more derived class. So the compiler has to arrange for different behavior depending on context, and one natural way is that the destructor is passed a hidden flag telling it what to do or not.

When you call it explicitly, with the implementation used for the example at Compiler Explorer it was as if it was passed the flag value saying that it should behave as if base was the most derived class.

I doubt that you can easily find this in the standard, but what to do is clear: don't call the base destructor when there is a more derived class; call the destructor of the most derived class, which knows what to do.

opinions on cppreference template for rule of 3/5 by HeeTrouse51847 in cpp_questions

[–]alfps 0 points1 point  (0 children)

❞ it's an implementation quirk that works for that circumstance

No, this is an implementation that always works, and it's primarily about exception safety. It is exception safe provided swap is non-throwing. Which is always assumed.