c++ – Improving constexpr invoke function C++17, alternative to std::invoke

I’ve learned that in C++17, std::invoke isn’t constexpr. To make a constexpr version, I could copy the implementation provided here: https://en.cppreference.com/w/cpp/utility/functional/invoke , OR I can complicate things by making my own version that relies on SFINAE, which may potentially be slow or have errors (which hopefully you’ll help me fix).

Below is my version:

template<std::size_t N> struct theCharArray{
  unsigned char theChar(N);  
};

template<class T> struct hasType;

template<class T, class U, class... Args> auto overloadInvoke(T f, U t1, Args&&... args)
-> theCharArray<(sizeof( hasType<decltype((static_cast<U&&>(t1).*f )(static_cast<Args&&>(args)...))>* ),1 )>;

template<class T, class U, class... Args> auto overloadInvoke(T f, U t1, Args&&... args)
-> theCharArray<(sizeof( hasType<decltype((t1.get().*f)(static_cast<Args&&>(args)...))>* ),2 )>;

template<class T, class U, class... Args> auto overloadInvoke(T f, U t1, Args&&... args)
-> theCharArray<(sizeof( hasType<decltype(((*static_cast<U&&>(t1)).*f)(static_cast<Args&&>(args)...))>* ),3 )>;

template<class T, class U> auto overloadInvoke(T f, U t1)
-> theCharArray<(sizeof( hasType<decltype(static_cast<U&&>(t1).*f)>* ),4 )>;

template<class T, class U> auto overloadInvoke(T f, U t1)
-> theCharArray<(sizeof( hasType<decltype(t1.get().*f)>* ),5 )>;

template<class T, class U> auto overloadInvoke(T f, U t1)
-> theCharArray<(sizeof( hasType<decltype((*static_cast<U&&>(t1)).*f)>* ),6 )>;

template <class T, class Type, class T1, class... Args>
constexpr decltype(auto) invoke(Type T::* f, T1&& t1, Args&&... args){
    constexpr auto chooseFxn = sizeof(overloadInvoke(f, t1, static_cast<Args&&>(args)...));
    if constexpr(chooseFxn == 1){
        return (static_cast<T1&&>(t1).*f )(static_cast<Args&&>(args)...);
    } else if constexpr(chooseFxn == 2){
        return (t1.get().*f)(static_cast<Args&&>(args)...);
    } else if constexpr(chooseFxn == 3){
        return ((*static_cast<T1&&>(t1)).*f)(static_cast<Args&&>(args)...);
    } else if constexpr(chooseFxn == 4){
        return static_cast<T1&&>(t1).*f;
    } else if constexpr(chooseFxn == 5){
        return t1.get().*f;
    } else {
        return (*static_cast<T1&&>(t1)).*f;
    }
}

template< class F, class... Args>
constexpr decltype(auto) invoke(F&& f, Args&&... args){
    return static_cast<F&&>(f)(static_cast<Args&&>(args)...);
}

I test the code here. How the SFINAE works is it checks all possible ways to invoke the arguments, and returns a struct whose size is determined by which invocation is well-formed, which I use to take the size of.

c++ – Writing a thread-safe ring queue in C++17

I tried implementing a thread-safe ring queue in C++. I’m totally new to move semantics and C++11/14/17 in general.

#ifndef THREAD_SAFE_RING_QUEUE_HPP_
#define THREAD_SAFE_RING_QUEUE_HPP_

#include <mutex>
#include <optional>
#include <vector>

namespace structure {
class MaximumCapacityReachedError : public std::runtime_error {
 public:
  MaximumCapacityReachedError(std::size_t capacity)
      : runtime_error("Structure instance is full (" +
                      std::to_string(capacity) + ")") {}
};

class EmptyError : public std::runtime_error {
 public:
  EmptyError() : runtime_error("Structure instance is empty") {}
};

template <class T>
class RingQueue {
 public:
  static constexpr std::size_t kDefaultCapacity_ = 10;

  RingQueue(std::size_t = kDefaultCapacity_);

  bool IsEmpty() const;
  std::size_t GetSize() const;
  void Push(T&& element);
  void TryPush(T&& element);
  T& ConsumeNext();
  std::optional<T> TryConsumeNext();
  void Clear();

  std::size_t GetCapacity() const noexcept;
  bool IsFull() const noexcept;

 private:
  mutable std::recursive_mutex mutex_;
  std::size_t capacity_ = 0;
  std::size_t head_ = 0;
  std::size_t tail_ = 0;
  std::vector<T> elements_;
  bool is_full_ = false;
};

template <class T>
inline RingQueue<T>::RingQueue(std::size_t capacity)
    : capacity_(capacity),
      elements_(std::vector<T>(capacity_)),
      is_full_(capacity == 0) {}

template <class T>
inline bool RingQueue<T>::IsEmpty() const {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);
  return capacity_ == 0 || (!is_full_ && head_ == tail_);
}

template <class T>
inline std::size_t RingQueue<T>::GetSize() const {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);

  if (is_full_) {
    return capacity_;
  }

  if (tail_ >= head_) {
    return tail_ - head_;
  }

  return capacity_ - (head_ - tail_);
}

template <class T>
inline void RingQueue<T>::Push(T&& element) {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);

  if (IsFull()) {
    throw MaximumCapacityReachedError(capacity_);
  }

  elements_(tail_) = std::forward<T>(element);
  tail_ = (tail_ + 1) % capacity_;
  is_full_ = head_ == tail_;
}

template <class T>
inline void RingQueue<T>::TryPush(T&& element) {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);

  if (IsFull()) {
    return;
  }

  elements_(tail_) = std::forward<T>(element);
  tail_ = (tail_ + 1) % capacity_;
  is_full_ = head_ == tail_;
}

template <class T>
inline T& RingQueue<T>::ConsumeNext() {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);

  if (IsEmpty()) {
    throw EmptyError();
  }

  const auto previous_head = head_;
  head_ = (head_ + 1) % capacity_;
  is_full_ = false;
  return std::forward<T>(elements_(previous_head));
}

template <class T>
inline std::optional<T> RingQueue<T>::TryConsumeNext() {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);

  if (IsEmpty()) {
    return std::nullopt;
  }

  const auto previous_head = head_;
  head_ = (head_ + 1) % capacity_;
  is_full_ = false;
  return std::forward<T>(elements_(previous_head));
}

template <class T>
inline void RingQueue<T>::Clear() {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);

  head_ = 0;
  tail_ = 0;
  is_full_ = false;
}

template <class T>
inline std::size_t RingQueue<T>::GetCapacity() const noexcept {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);
  return capacity_;
}

template <class T>
inline bool RingQueue<T>::IsFull() const noexcept {
  std::scoped_lock<std::recursive_mutex> lock(mutex_);
  return is_full_;
}
}  // namespace structure

#endif  // THREAD_SAFE_RING_QUEUE_HPP_

I have tested my code and it seems to work, but so many things can go wrong…

EDIT: here are the tests that I’ve done using Catch2. DummyObject is just a class containing an int (the value may be passed in the constructor, and is 0 by default).

#include <memory>
#include "catch.hpp"
#include "dummies.hpp"
#include "utils/structure/ring_queue.hpp"

TEST_CASE("Ring queue creation with default capacity", "(structure)") {
  auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>();

  SECTION("Properties after creation with default capacity.") {
    REQUIRE(queue.GetCapacity() == queue.kDefaultCapacity_);
    REQUIRE(queue.GetSize() == 0);
    REQUIRE(!queue.IsFull());
    REQUIRE(queue.IsEmpty());
  }
}

TEST_CASE("Ring queue creation with specific capacity", "(structure)") {
  const std::size_t capacity = 15;

  auto queue =
      structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(capacity);

  SECTION("Properties after creation with specific capacity.") {
    REQUIRE(queue.GetCapacity() == capacity);
    REQUIRE(queue.GetSize() == 0);
    REQUIRE(!queue.IsFull());
    REQUIRE(queue.IsEmpty());
  }
}

TEST_CASE("Ring queue push operations in single thread", "(structure)") {
  const std::size_t capacity = 15;

  SECTION("Properties after pushing one element.") {
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    const auto dummy1 = dummy::DummyObject(0);
    queue.Push(std::make_unique<dummy::DummyObject>(0));

    REQUIRE(queue.GetSize() == 1);
    REQUIRE(!queue.IsFull());
    REQUIRE(!queue.IsEmpty());
  }

  SECTION("Properties after pushing two elements.") {
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    queue.Push(std::make_unique<dummy::DummyObject>(0));
    queue.Push(std::make_unique<dummy::DummyObject>(0));
    REQUIRE(queue.GetSize() == 2);
    REQUIRE(!queue.IsFull());
    REQUIRE(!queue.IsEmpty());
  }

  SECTION("Properties after pushing the maximum amount of elements.") {
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(1);

    queue.Push(std::make_unique<dummy::DummyObject>(0));
    REQUIRE(queue.GetSize() == 1);
    REQUIRE(queue.IsFull());
    REQUIRE(!queue.IsEmpty());
  }

  SECTION("Properties after pushing too many elements.") {
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(1);

    queue.Push(std::make_unique<dummy::DummyObject>(0));

    bool is_exception = false;

    try {
      queue.Push(std::make_unique<dummy::DummyObject>(0));
    } catch (const structure::MaximumCapacityReachedError& error) {
      is_exception = true;
    }

    REQUIRE(is_exception);
    REQUIRE(queue.GetSize() == 1);
    REQUIRE(queue.IsFull());
    REQUIRE(!queue.IsEmpty());
  }

  SECTION("Specific error case: pushing one element in a 0-capacity queue.") {
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(0);

    bool is_exception = false;

    try {
      queue.Push(std::make_unique<dummy::DummyObject>(0));
    } catch (const structure::MaximumCapacityReachedError& error) {
      is_exception = true;
    }

    REQUIRE(is_exception);
  }

  SECTION("Try pushing an element.") {
    std::size_t capacity = 5;
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    for (std::size_t index = 0; index < capacity + 1; index++) {
      queue.TryPush(std::make_unique<dummy::DummyObject>(0));
    }

    REQUIRE(queue.GetSize() == capacity);
  }
}

TEST_CASE("Ring queue consume operations in single thread",
          "(structure)") {
  const std::size_t capacity = 15;

  SECTION("Properties after consuming one element.") {
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    queue.Push(std::make_unique<dummy::DummyObject>(42));
    REQUIRE(queue.ConsumeNext()->GetValue() == 42);
    REQUIRE(queue.GetSize() == 0);
    REQUIRE(!queue.IsFull());
    REQUIRE(queue.IsEmpty());
  }

  SECTION("Properties after consuming two elements.") {
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    queue.Push(std::make_unique<dummy::DummyObject>(42));
    REQUIRE(queue.ConsumeNext()->GetValue() == 42);
    REQUIRE(queue.GetSize() == 0);
    REQUIRE(!queue.IsFull());
    REQUIRE(queue.IsEmpty());
  }

  SECTION("Properties after consuming one element in a two-element queue.") {
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    queue.Push(std::make_unique<dummy::DummyObject>(0));
    queue.Push(std::make_unique<dummy::DummyObject>(0));
    REQUIRE(queue.ConsumeNext());
    REQUIRE(queue.GetSize() == 1);
    REQUIRE(!queue.IsFull());
    REQUIRE(!queue.IsEmpty());
  }

  SECTION("Properties after consuming too many elements.") {
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(1);

    queue.Push(std::make_unique<dummy::DummyObject>(42));
    queue.ConsumeNext();
    bool is_exception = false;

    try {
      queue.ConsumeNext();
    } catch (const structure::EmptyError& error) {
      is_exception = true;
    }

    REQUIRE(is_exception);
    REQUIRE(queue.GetSize() == 0);
    REQUIRE(!queue.IsFull());
    REQUIRE(queue.IsEmpty());
  }

  SECTION("Order is respected.") {
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(3);

    queue.Push(std::make_unique<dummy::DummyObject>(1));
    queue.Push(std::make_unique<dummy::DummyObject>(2));
    queue.Push(std::make_unique<dummy::DummyObject>(3));

    REQUIRE(queue.ConsumeNext().get()->GetValue() == 1);
    REQUIRE(queue.ConsumeNext().get()->GetValue() == 2);
    REQUIRE(queue.ConsumeNext().get()->GetValue() == 3);
  }

  SECTION("Specific error case: consuming one element in a 0-capacity queue.") {
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(0);

    bool is_exception = false;

    try {
      queue.ConsumeNext();
    } catch (const structure::EmptyError& error) {
      is_exception = true;
    }

    REQUIRE(is_exception);
  }

  SECTION("Try consuming an element.") {
    std::size_t capacity = 5;
    auto queue = structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(
        capacity);

    for (std::size_t index = 0; index < capacity; index++) {
      queue.Push(std::make_unique<dummy::DummyObject>(0));
    }

    for (std::size_t index = 0; index < capacity + 1; index++) {
      const auto element = queue.TryConsumeNext();
      const bool is_element = element.has_value();
      const bool is_okay = index < capacity ? is_element : !is_element;
      REQUIRE(is_okay);
    }

    REQUIRE(queue.GetSize() == 0);
  }
}

TEST_CASE("Ring queue pushing operations in multiple threads",
          "(structure)") {
  const std::size_t capacity = 15;
  SECTION("Usage in threads: push operations.") {
    std::size_t thread_number = 5;
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(5);

    std::vector<std::thread> threads;

    for (std::size_t index = 0; index < thread_number; index++) {
      threads.push_back(std::thread((&queue)() {
        queue.Push(std::make_unique<dummy::DummyObject>(0));
      }));
    }

    std::for_each(threads.begin(), threads.end(),
                  ()(std::thread& thread) { thread.join(); });

    REQUIRE(queue.GetSize() == thread_number);
  }
}

TEST_CASE("Ring queue consume operations in multiple threads",
          "(structure)") {
  SECTION("Usage in threads: consume operations.") {
    std::size_t thread_number = 5;
    auto queue =
        structure::RingQueue<std::shared_ptr<dummy::DummyObject>>(5);

    std::vector<std::thread> threads;

    for (std::size_t index = 0; index < thread_number; index++) {
      queue.Push(std::make_unique<dummy::DummyObject>(0));
    }

    for (std::size_t index = 0; index < thread_number; index++) {
      threads.push_back(std::thread((&queue)() { queue.ConsumeNext(); }));
    }

    std::for_each(threads.begin(), threads.end(),
                  ()(std::thread& thread) { thread.join(); });

    REQUIRE(queue.GetSize() == 0);
  }
}
```

Realme C17, Xiaomi Redmi 9T, Xiaomi Redmi Note 9, Oppo A53. (phone recommendation) [closed]

It’s pretty self-explanatory. I need a new phone and i’ve narrowed it to these four phones that i think is suitable for my price range, which one is the best and also what are the pros and cons on each phone?

c++ – C++17 Recursive Fibonacci calculation with memoization

Compiler is g++ 4.2. I’m new to C++, but I’ve done a lot of data science, web scraping, and some socketing stuff in Python. This code generates the nth Fibonacci number, either with a naive implementation or with caching.

#include <iostream>
#include <string>
#include <unordered_map>
#define BIG unsigned long long int

std::unordered_map<int, BIG> umap;


int fib(int n) {
    if (n < 2) {
        return n;
    }
    return fib(n-1) + fib(n-2);
}

BIG fib_with_memo(int n) {
    if (umap.find(n) == umap.end()) { // if F_n not in cache, calculate, cache, and return it
        BIG val = fib_with_memo(n-1) + fib_with_memo(n-2);
        umap(n) = val;
        return val;
    }
    return umap(n); // otherwise return the cached value
}

int main(int argc, char** argv) {
    umap(0) = 0;
    umap(1) = 1;

    int input = std::stoi( std::string(argv(1)) );
    
    std::cout << fib_with_memo(input) << std::endl;
    return 0;
}

I used a std::unordered_map (hash table) because its lookup/insertion times are $O(1)$ (compared to the std::map, which has $O(log(n))$ for both.)

c++ – Snake game in C++17 with SDL2

I implemented a simple snake clone in C++, using SDL2 for the graphics part. Gameplay-wise, its pretty much classic snake: The player is able to control the snake with “WASD”, food gets spawned randomly and if you eat it, you gain speed and length. The game is lost once you collide with yourself. Any tips?

source.cpp

#include <SDL.h>

#include "Framework.h"
#include "Game.h"

int main(int argc, char* argv())
{   
    Snake::SDL_Framework framework("Snake", 800, 800);
    Snake::Game game(framework);

    game.run();

    return 0;
}

Framework.h

#ifndef SDL_FRAMEWORK
#define SDL_FRAMEWORK

#include <SDL.h>

#include <cstdint>
#include <string>
#include <exception>

namespace Snake
{
    class SDL_Framework
    {
    public:

        SDL_Framework() : main_window{ nullptr }, renderer{ nullptr }, window_height{ 0 }, window_width{ 0 } {};
        SDL_Framework(const std::string& window_name, int32_t height, int32_t width);
        ~SDL_Framework();

        void clear();
        void update();
        int32_t get_height() const { return window_height; }
        int32_t get_width() const { return window_width; }

        SDL_Renderer* renderer;

    private:

        SDL_Window* main_window;
        int32_t window_height;
        int32_t window_width;

    };
}

#endif

Framework.cpp

#include "Framework.h"

static void process_error(const std::string& msg)
{
    throw std::runtime_error(msg);
}

namespace Snake
{
    SDL_Framework::SDL_Framework(const std::string& window_name, int32_t height, int32_t width)
    {
        if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0)
        {
            process_error(std::string("Error while trying to initialise SDL! SDL_Error: ") + SDL_GetError());
        }

        main_window = SDL_CreateWindow(window_name.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, height, width, SDL_WINDOW_SHOWN);
        if (main_window == nullptr)
        {
            process_error(std::string("Error while trying to create SDL_Window! SDL_Error: ") + SDL_GetError());
        }

        window_height = height;
        window_width = width;

        renderer = SDL_CreateRenderer(main_window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
        if (renderer == nullptr)
        {
            process_error(std::string("Error while trying to create renderer! SDL_Error: ") + SDL_GetError());
        }

        SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
    }

    SDL_Framework::~SDL_Framework()
    {
        SDL_Quit();
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(main_window);
    }

    void SDL_Framework::clear()
    {
        SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
        SDL_RenderClear(renderer);
    }

    void SDL_Framework::update()
    {
        SDL_RenderPresent(renderer);
    }
}

Game.h

#ifndef SNAKE_GAME
#define SNAKE_GAME

#include "Framework.h"
#include "Board.h"
#include "Player.h"

namespace Snake
{
    class Game
    {
    public:

        Game(const SDL_Framework& uframework);

        void run();
        void update();
        void render();

    private:

        SDL_Framework framework;
        Board board;
        Player player;

        int32_t frame;

    };
}

#endif

Game.cpp

#include "Game.h"

namespace Snake
{
    Game::Game(const SDL_Framework& uframework) : frame{0}
    {
        framework = uframework;

        int32_t cells_per_side = 25;
        int32_t cell_size = framework.get_height() / cells_per_side;

        board = Board(framework.get_height(), framework.get_width(), cell_size);
        board.generate_food();

        int32_t middle = cell_size * (framework.get_height() / cell_size / 2); // calculating the middle of the board in cell roster, assuming length == width
        player = Player(middle, middle);
    }

    void Game::run()
    {
        bool running = true;
        SDL_Event event;

        do {

            frame++;

            while (SDL_PollEvent(&event) != 0)
            {
                switch (event.type)
                {
                case SDL_QUIT:
                {
                    running = false;
                    break;
                }
                }
            }

            update();
            framework.clear();
            render();
            framework.update();

        } while (running);
    }

    void Game::update()
    {
        if (player.check_food_collision(board.get_food_pos()))
        {
            board.generate_food();
        }

        player.update(board.get_borders(), board.get_cell_size(), frame);
    }

    void Game::render()
    {
        board.render(framework.renderer);
        player.render(framework.renderer, board.get_cell_size());
    }
}

Board.h

#ifndef SNAKE_BOARD
#define SNAKE_BOARD

#include <SDL.h>

#include <utility>
#include <random>

#include "Cell.h"

namespace Snake
{
    class Board
    {
    public:

        Board() = default;
        Board(int32_t ulength, int32_t uwidth, int32_t cell_size);

        void generate_food();
        void render(SDL_Renderer* renderer) const;
        std::pair<int32_t, int32_t> get_food_pos() const { return food.get_pos(); }
        std::pair<int32_t, int32_t> get_borders() { return { length, width }; }
        int32_t get_cell_size() const { return cell_size; }

    private:

        Cell food;
        int32_t length;
        int32_t width;
        int32_t cell_size;
        std::mt19937 gen;
        std::uniform_int_distribution<int32_t> distr;

    };
}

#endif

Board.cpp

#include "Board.h"

namespace Snake
{
    Board::Board(int32_t ulength, int32_t uwidth, int32_t ucell_size) : length{ ulength }, width{ uwidth }, cell_size{ ucell_size }
    {
        gen = std::mt19937(std::random_device{}());
        distr = std::uniform_int_distribution<int32_t>(0, (length / cell_size) - 1);
    }

    void Board::generate_food()
    {
        int32_t food_x = distr(gen) * cell_size;
        int32_t food_y = distr(gen) * cell_size;

        food.update_pos(food_x, food_y);
    }

    void Board::render(SDL_Renderer* renderer) const
    {
        SDL_SetRenderDrawColor(framework.renderer, 0, 0, 0, 255);

        // render cell grid
        for (int32_t distance = cell_size; distance < length; distance += cell_size)
        {
            SDL_RenderDrawLine(renderer, distance, 0, distance, length);
            SDL_RenderDrawLine(renderer, 0, distance, width, distance);
        }

        // render food
        food.render(renderer, cell_size, SDL_Color{ 0, 0, 0, 255 });
    }
}

Player.h

#ifndef SNAKE_PLAYER
#define SNAKE_PLAYER

#include <SDL.h>

#include <vector>

#include "Cell.h"

namespace Snake
{
    class Player
    {
        enum Direction
        {
            UP, DOWN, RIGHT, LEFT
        };

    public:

        Player() = default;
        Player(int32_t x, int32_t y) :
            length{ 1 },
            growth_remaining{ 2 },
            growth_per_cell{ 2 },
            cur_direction{LEFT},
            speed{ 30 },
            speed_gain{2},
            max_speed{ 4 }
        {
            head.update_pos(x, y);
        }

        void update(std::pair<int32_t, int32_t> borders, int32_t cell_size, int32_t frame);
        void render(SDL_Renderer* renderer, int32_t cell_size) const;
        bool check_food_collision(std::pair<int32_t, int32_t> food_pos);

    private:

        void update_position(std::pair<int32_t, int32_t> borders, int32_t cell_size, int32_t frame);
        void update_direction();
        bool check_self_collision();
        void reset();

        int32_t length;
        int32_t growth_remaining;
        int32_t growth_per_cell;
        Direction cur_direction;
        int32_t speed; // lower is faster, updates player every {speed} frames
        int32_t speed_gain;
        int32_t max_speed;

        Cell head;
        std::vector<Cell> body;

        std::pair<int32_t, int32_t> get_next_pos(int32_t cell_size, std::pair<int32_t, int32_t> cur_pos);

    };
}

#endif

Player.cpp

#include "Player.h"

namespace Snake
{
    void Player::update(std::pair<int32_t, int32_t> borders, int32_t cell_size, int32_t frame)
    {
        update_direction();
        update_position(borders, cell_size, frame);
        
        if (check_self_collision())
        {
            reset();
        }
    }

    void Player::update_position(std::pair<int32_t, int32_t> borders, int32_t cell_size, int32_t frame)
    {
        if (frame % speed) return; // only update when neccessary

        if (growth_remaining)
        {
            body.insert(std::begin(body), Cell(head.get_pos().first, head.get_pos().second));

            growth_remaining--;
        }
        else
        {
            if (!body.empty())
            {
                for (uint32_t i = body.size() - 1; i > 0; --i)
                {
                    body(i).update_pos(body(i-1).get_pos().first, body(i - 1).get_pos().second);
                }
                body(0).update_pos(head.get_pos().first, head.get_pos().second);
            }
        }

        auto new_pos = get_next_pos(cell_size, head.get_pos());

        // implement player moving over border
        if (new_pos.first > borders.second) { new_pos.first = 0; }
        if (new_pos.first < 0) { new_pos.first = borders.second - cell_size; }
        if (new_pos.second > borders.first) { new_pos.second = 0; }
        if (new_pos.second < 0) { new_pos.second = borders.first - cell_size; }

        head.update_pos(new_pos.first, new_pos.second);
    }

    void Player::update_direction()
{
        const Uint8* key_state = SDL_GetKeyboardState(nullptr);

        if (key_state(SDL_SCANCODE_W))
        {
            cur_direction = UP;
        }
        else if (key_state(SDL_SCANCODE_S))
        {
            cur_direction = DOWN;
        }
        else if (key_state(SDL_SCANCODE_A))
        {
            cur_direction = LEFT;
        }
        else if (key_state(SDL_SCANCODE_D))
        {
            cur_direction = RIGHT;
        }
    }

    bool Player::check_self_collision()
    {
        for (auto& bodypart : body)
        {
            if (head.get_pos() == bodypart.get_pos())
            {
                return true;
            }
        }

        return false;
    }

    void Player::reset()
    {
        body.clear();
        growth_remaining = 2;
        speed = 30;
    }

    void Player::render(SDL_Renderer* renderer, int32_t cell_size) const
    {
        head.render(renderer, cell_size, SDL_Color{255, 100, 0, 255});

        for (auto& cell : body)
        {
            cell.render(renderer, cell_size);
        }
    }

    bool Player::check_food_collision(std::pair<int32_t, int32_t> food_pos)
    {
        if (head.get_pos() == food_pos)
        {
            growth_remaining += growth_per_cell;

            speed -= speed_gain; // increase speed
            if (speed < max_speed) { speed = max_speed; }

            return true;
        }
        
        return false;
    }


    std::pair<int32_t, int32_t> Player::get_next_pos(int32_t cell_size, std::pair<int32_t, int32_t> cur_pos)
    {
        std::pair<int32_t, int32_t> next_pos(cur_pos);

        switch (cur_direction)
        {
            case UP:
            {
                next_pos.second -= cell_size;
                break;
            }
            case DOWN:
            {
                next_pos.second += cell_size;
                break;
            }
            case RIGHT:
            {
                next_pos.first += cell_size;
                break;
            }
            case LEFT:
            {
                next_pos.first -= cell_size;
                break;
            }
        }

        return next_pos;
    }
}

Cell.h

#ifndef SNAKE_CELL
#define SNAKE_CELL

#include <SDL.h>

#include <cstdint>
#include <utility>

namespace Snake
{
    class Cell
    {
    public:

        Cell() = default;
        Cell(int32_t x, int32_t y) : 
            pos{ x, y }
        {}

        void render(SDL_Renderer* renderer, int32_t cell_size, SDL_Color color = SDL_Color{ 255, 30, 20, 255 }) const;
        void update_pos(int32_t x, int32_t y) { pos.first = x; pos.second = y; }
        std::pair<int32_t, int32_t> get_pos() const { return pos; }

    private:

        std::pair<int32_t, int32_t> pos;

    };
}

#endif

Cell.cpp

#include "Cell.h"

namespace Snake
{
    void Cell::render(SDL_Renderer* renderer, int32_t cell_size, SDL_Color color) const
    {
        // set draw color to red
        SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);

        SDL_Rect rect = { pos.first + 1, pos.second + 1, cell_size - 1, cell_size - 1 };
        SDL_RenderFillRect(renderer, &rect);
    }
}

c++ – C++17 multi threaded thread “pauser”

I needed a way to pause multiple threads from a single parent thread, this was my solution for the general case. I would like advice on code quality and enhancements. I’m particularly interested if there are any weird edge case usages where this fails in terms of synchronization, and what I should do to prevent them.

//threadpause.h
#ifndef UTIL_THREADPAUSE_H
#define UTIL_THREADPAUSE_H
#include <condition_variable>
#include <mutex>
namespace util {
    class ThreadPause{
    public:
        explicit ThreadPause(bool paused = false);
        ((nodiscard))
        bool is_paused() const;
        void wait();
        void pause();
        void resume();
    private:
        bool m_paused;
        std::mutex m_mutex;
        std::condition_variable m_cv;
    };
}

#endif //UTIL_THREADPAUSE_H

|

//threadpause.cpp
#include "threadpause.h"

util::ThreadPause::ThreadPause(bool paused) : m_paused(paused){

}

bool util::ThreadPause::is_paused() const {
    return m_paused;
}

void util::ThreadPause::wait() {
    while(is_paused()){
        std::unique_lock<std::mutex> lock(m_mutex);
        //need the condition in here in case of spurious wake up, 
        //wont exit condition unless condition true
        m_cv.wait(lock, (&paused = m_paused){return !paused;});
    }
}

void util::ThreadPause::pause() {
    //lock around the variable so with can modify it
    std::unique_lock<std::mutex> lock(m_mutex);
    m_paused = true;
}

void util::ThreadPause::resume() {
    //lock around the variable so with can modify it
    std::unique_lock<std::mutex> lock(m_mutex);
    m_paused = false;
    //unlock to stop threads from immediately locking when notify is called.
    lock.unlock();
    m_cv.notify_all();
}

|

//example useage main.cpp
#include "threadpause.h"
#include <thread>
#include <atomic>
#include <chrono>
#include <vector>
#include <iostream>
int main(){
    using namespace std::chrono_literals;

    std::size_t thread_count = 10;
    std::atomic<bool> exit_threads(false);
    util::ThreadPause thread_pause;
    std::vector<int> increments(thread_count, 0);
    auto thread_function = ()(
            std::atomic<bool>& exit_threads,
            util::ThreadPause& thread_pause,
            int& increment){
        while(!(exit_threads.load())){
            thread_pause.wait();
            //... your work would normally go here
            increment += 10;
        }
    };
    std::vector<std::thread> threads;
    for(std::size_t i = 0; i < thread_count; ++i){
        threads.emplace_back(thread_function,
                             std::ref(exit_threads),
                             std::ref(thread_pause),
                             std::ref(increments(i)));
    }
    std::this_thread::sleep_for(100ms);
    thread_pause.pause();
    std::this_thread::sleep_for(100ms);
    std::cout << "Current values for increments: n";
    for(auto increment : increments){
        std::cout << increment << "n";
    }
    std::this_thread::sleep_for(100ms);
    std::cout << "Pause values for increments: n";
    for(auto increment : increments){
        std::cout << increment << "n";
    }
    thread_pause.resume();
    std::this_thread::sleep_for(100ms);
    thread_pause.pause();
    std::this_thread::sleep_for(100ms);
    std::cout << "Resume values for increments: n";
    for(auto increment : increments){
        std::cout << increment << "n";
    }
    thread_pause.resume();
    exit_threads.store(true);
    for(auto& thread : threads){
        thread.join();
    }
    std::cout << "Threads Joined!n";
    return 0;
}

c++17 – C++ shared pointer wrapper for lazy initialization

I have written a very simple wrapper around std::shared_ptr that supports lazy initialization. Do you see any problems?

#include <functional>
#include <tuple>
#include <utility>

template <class T, typename ... Args>
class LazySharedPtr {
public:
    LazySharedPtr(Args ... args) :
        ptr(nullptr) {
        this->init = (args = std::make_tuple(std::forward<Args>(args) ...))() mutable {
            return std::apply(()(auto&& ... args) {
                return std::make_shared<T>(std::forward<Args>(args) ...);
                }, std::move(args));
        };
    }

    virtual ~LazySharedPtr() = default;

    bool IsInited() const noexcept {
        return ptr != nullptr;
    }

    void Init() {
        this->InitAndGet();
    }

    std::shared_ptr<T> Get() {
        return (ptr) ? ptr : InitAndGet();
    }

    const std::shared_ptr<T> Get() const {
        return (ptr) ? ptr : InitAndGet();      
    }

    T* operator ->() {
        return this->Get().get();
    }

    const T* operator ->() const {
        return this->Get().get();
    }

    explicit operator bool() const noexcept {
        return this->IsInited();
    }

protected:
    std::function<std::shared_ptr<T>()> init;
    mutable std::shared_ptr<T> ptr;

    std::shared_ptr<T> InitAndGet() const {
        ptr = this->init();
        return ptr;
    }
};

Note: Visual Studio report warning for this->Get().get():

Warning C26815 The pointer is dangling because it points at a
temporary instance which was destroyed.

However, I dont see why, because shared_ptr is owned by the class so there should alway be at least one instance “alive”. Imho, This warning is not reported by compiler, only by Intellisense.

c++ – Design of Experiments data structure with C++17

Because you’re defining your member functions within the class declaration, you don’t need to use the inline keyword. All member functions declared in the class declaration are implicitly inline (including the default and copy constructors) are inline.

In your copy constructor, you can make use of member initializers to construct the copied maps directly, rather than default construct and assign:

doe_model(const doe_model& doe): required_explorations(doe.required_explorations),
    next(required_explorations.end()), number_of_explorations(doe.number_of_explorations)
}

There are a few places where you’re using two statements but they can be combined.

In update_config, you can decrement in the test:

if (--number_of_explorations.at(config_id) <= 0)

In get_next, you can combine the iterator increment and return statement:

return ++next;

This also uses the preincrement on the iterator, as it avoids creating a copy of the original iterator that is returned by the postincrement version, then immediately discarded.

In add_config, you have the potential of adding a config with a higher number of required explorations than are provided if that config already exists (if required_explorations is assigned to, no change is made to number_of_explorations). This may or may not be a problem.

You should consider adding a move constructor and a move assignment operator, which can avoid creating copies.

c++17 – Are there any guidelines on naming lambda functions?

I am new to using lambda functions.

I wrote the following:

void CChristianLifeMinistryEntry::AddChairmanNotesToXML(tinyxml2::XMLDocument& rDoc, tinyxml2::XMLElement* pWeek)
{
    tinyxml2::XMLElement* pChairmanNotes = InsertNewElement(rDoc, pWeek, "ChairmanNotes");

    auto AddChairmanNote = ()(tinyxml2::XMLDocument& rDoc, tinyxml2::XMLElement* pChairmanNotes, CString strNote, LPCSTR szKey)
    {
        if (!strNote.IsEmpty())
        {
            CString strEncodedNote = strNote;
            strEncodedNote.Replace(_T("rn"), _T("<br/>"));
            InsertNewElement(rDoc, pChairmanNotes, szKey, strEncodedNote);
        }
    };

    AddChairmanNote(rDoc, pChairmanNotes, GetNotesOpeningComments(), kOpeningComments);
    AddChairmanNote(rDoc, pChairmanNotes, GetNotesClosingComments(), kClosingComments);
    AddChairmanNote(rDoc, pChairmanNotes, GetNotesTreasures1(), "Treasures1");
    AddChairmanNote(rDoc, pChairmanNotes, GetNotesTreasures2(), "Treasures2");
    AddChairmanNote(rDoc, pChairmanNotes, GetNotesLiving1(), "Living1");
    AddChairmanNote(rDoc, pChairmanNotes, GetNotesLiving2(), "Living2");
    AddChairmanNote(rDoc, pChairmanNotes, GetNotesLiving3(), "Living3");
}

Are there any guidelines on naming lambda functions?

c++17 – C++ callback multithreaded, can unregister itself

With this post, i would like to 1) ask for feedback on below code as it stands (do i apply all best practices for c++20, is it safe), and 2) ask for advice on how to add new functionality to my class.

Based on code and design advice found on stackoverflow (e.g. here), I have written a class that handles listeners registering callbacks to receive messages in multithreaded code (listeners can be added from different threads than the thread that messages are sent from, only one thread every sends messages). This code also needed multiple ways for listeners to unregister themselves:

  1. when the class owning the receiver destructs or is otherwise done receiving: hence add_listener() returns a cookie in the form of a std::list::const_iterator (list because iterators to none of the other elements are invalidated when an element is removed, and other cookies thus stay valid)
  2. when the callback during execution determines it no longer needs further messages, this is handled by returning true (“remove myself please”).

Regarding advice for new functionality: I now run into the situation where a listener callback, when invoked, needs to replace itself with another callback. I can’t call add_listener() as that would deadlock when called from inside an invoked callback. Unless i move to use a std::recursive_mutex instead. That seems heavy. Instead i thought that the callback in this case could return the new callback to register, which would replace the currently registered one (the cookie thus stays valid). But I do not know what my listener’s function signature would then be, as you cannot do something CRTP-like with using statements (using listener = std::function<listener(Message...)>; is invalid) (NB: in reality if this option were possible i’d return a variant with bool as well i guess, as i need to also maintain self-deregistration functionality.)

The alternative would be to add a replacement queue to the broadcaster storing a pair of <listenerCookie, listener> which lists what updates should be done once safe. When this list is not empty at end of the notify_all function, the current callback is replaced with the new one in the update list. This guarantees that the old callback is never called again and that the cookie held stays valid but now points to the new callback. But this design seems complicated: it would require adding an extra member function to the broadcaster class, which should only be called from inside a callback. And that is easy to misuse unless code comments saying to only invoke from inside a callback are honored (or is there a way to ensure a function can only be called by the invoked callback? I have not found one).

So yeah, two rather complicated solutions or a recursive mutex, am i missing some simpler solution?

#include <list>
#include <mutex>
#include <functional>

template<class... Message>
class Broadcaster
{
public:
    using listener       = std::function<bool(Message...)>;
    using listenerCookie = typename std::list<listener>::const_iterator;

    listenerCookie add_listener(listener&& r_)
    {
        auto l = lock();
        return targets.emplace(targets.cend(), std::move(r_));
    }
    void remove_listener(listenerCookie it_)
    {
        auto l = lock();
        remove_listener_impl(it_);
    }

    void notify_all(Message... msg_)
    {
        auto l = lock();

        for (auto i = targets.cbegin(), e = targets.cend(); i != e; )
            if ((*i)(msg_...))
                i = remove_listener_impl(i);
            else
                ++i;
    }

private:
    auto lock()
    {
        return std::unique_lock<std::mutex>(m);
    }
    listenerCookie remove_listener_impl(listenerCookie it_)
    {
        return targets.erase(it_);
    }

private:
    std::mutex m;
    std::list<listener> targets;
};