mercredi 3 août 2022

C++: derived structure layout and copiability

I have the following structs

#pragma pack(push)
struct COMMON {
    uint16_t type;
    uint16_t m;
    uint16_t o;
    uint16_t f;
    uint64_t s;
};
#pragma pack(pop)

#pragma pack(push)
struct A_HEADER : public COMMON {
    uint16_t g;
    uint16_t c;
};
#pragma pack(pop)

#pragma pack(push)
struct B_HEADER : public COMMON {
    uint32_t r;
    uint32_t h;
};
#pragma pack(pop)

now I know that the A_HEADER and B_HEADER are trivially copyable but are not standard layout. As far as I know, the former property means that they can be memcpyd safely, while the latter means that there is no guaranteed byte representation for those tho structs.

Now I need to send remotely (via UDP) both A_HEADER and B_HEADER. When the receiver got the message in some buffer, it does not know whether it is A_HEADER or B_HEADER and have to decide how to decode it depending on the field type contained on the COMMON.

In the receiver machine I was thinking something like

void print_header(const uint8_t* buf)
{
   const COMMON* com = reinterpret_cast<const COMMON*>(buf)

   switch (com->type) {
   case A_TYPE:
        print_acq_details(reinterpret_cast<const A_HEADER*>(com));
        break;
   case B_TYPE:
        print_cal_details(reinterpret_cast<const B_HEADER*>(com));
        break;
   }
}

but I have a sort of dissonance in my mind: according to the fact that both structs are trivially copyable and that a base pointer can point to some base-derived class it seems to me that what I wrote is safe; on the other side the fact that the derived struct above are not standard layout makes me worry. Perhaps, since the layout is compiler dependent, I was thinking that everything works as far as I have exactly same compiler on both machines (which have the same architecture).

What is the correct reasoning (if any)?

NOTICE

I know one solution would be to incapsulate COMMON inside A_HEADER and B_HEADER, in such a way to obtain POD, but I cannot modify such part of code.

How to build a type generator class based on the input data type and container type(by template arguments)?

There exists two basic data types in my tiny demo program, represented by the below classes:

struct FloatDataTypeDescriptor {
  using dtype = float;
};
struct Uint8DataTypeDescriptor {
  using dtype = uint8_t;
  uint8_t zero_point_;
  float scale_;
};

Conceptually, the data type descriptor and data actual holder(might be std::array, std::unique_ptr, std::vector...) are tightly couple together, So i decided to use std::pair to represent the data chunk, like:

using ChunkTypeA = std::pair<FloatDataTypeDescriptor, std::vector<FloatDataTypeDescriptor::dtype>>;
using ChunkTypeB = std::pair<Uint8DataTypeDescriptor, std::vector<Uint8DataTypeDescriptor::dtype>>;
using ChunkTypeC = std::pair<FloatDataTypeDescriptor, std::unique_ptr<FloatDataTypeDescriptor::dtype[]>;
// ...

This can work though, but writing such template alias all over the place is a little bit of tedious. So i've thought of using partial specialization to create a "type generator", produce the needed std::pair<> type by provided templates argument.

// primary template
template <typename TypeDescriptor, template<typename, typename...> class Container>
struct PairedTypeGenerator;

// partial specialization for std::vector
template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>> {
  using type = std::pair<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>>;
};

And use it like:

using a = PairedTypeGenerator<Uint8TypeDescriptor, std::vector>::type;

I've tried to use variadic template pack in the template template parameter Container. Since some Container might need extra argument other than the data type(like vector Allocator / unique_ptr Deleter). It didn't work, clang tolds me:

<source>:21:53: error: template argument for template template parameter must be a class template or type alias template
struct PairedTypeGenerator<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>> {

Thanks to @463035818_is_not_a_number liberal and great advice, i continue to add more specialization for std::vector / std::unique_ptr / std::array

template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::vector> {
  using type = std::pair<TypeDescriptor, std::vector<typename TypeDescriptor::dtype>>;
};

template <typename TypeDescriptor>
struct PairedTypeGenerator<TypeDescriptor, std::unique_ptr> {
  using type = std::pair<TypeDescriptor, std::unique_ptr<typename TypeDescriptor::dtype[]>>;
};


template <typename TypeDescriptor, typename Deleter>
struct PairedTypeGenerator<TypeDescriptor, std::unique_ptr, Deleter> {
  using type = std::pair<TypeDescriptor, std::unique_ptr<typename TypeDescriptor::dtype[], Deleter>>;
};

So now i can support case that std::unique_ptr with custom Deleter, just use it as:

using Ptr = EmbeddingPairedTypeGenerator<Uint8EmbeddingDataTypeDescriptor, std::unique_ptr, decltype(&std::free)>::type;

However, for std::array, things become much tricker, the std::array Container type need a non type template parameter, which cannot be matched by the parameter pack. I just want to use it by some syntax similar as:

using Array = PairedTypeGenerator<Uint8DataTypeDescriptor, std::array, 512>;

lundi 1 août 2022

mktime returning the wrong time

I have two functions for creating a date string, and parsing a date string. The creation works, but the parser seems to add 5 hours to the time. For context, my time zone is EST, 5 hours behind GMT. How can this be fixed? Below are the functions:

std::string build_date(time_t rawtime) {
    struct tm* timeinfo = gmtime(&rawtime);
    std::stringstream ss;
    ss.imbue(std::locale(setlocale(LC_ALL, "C")));
    ss << std::put_time(timeinfo, "%a, %d %b %Y %T %Z");
    return ss.str();
}

time_t parse_date(const std::string& date) {
    struct tm timeinfo = {0};
    std::istringstream ss(date);
    ss.imbue(std::locale(setlocale(LC_ALL, "C")));
    ss >> std::get_time(&timeinfo, "%a, %d %b %Y %T %Z");
    return mktime(&timeinfo);
}

Maximum Sum of Non-Adjacent Elements runtime error addition of unsigned offset

I was solving house robber problem of leetcode using dynamic programming and leetcode is giving me Line 1034: Char 34: runtime error: addition of unsigned offset to 0x6020000000b0 overflowed to 0x6020000000ac (stl_vector.h) SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_vector.h:1043:34

where did i go wrong ?

class Solution {
public:
int solveUtil(int ind, vector<int>& arr, vector<int>& dp){

if(dp[ind]!=-1) return dp[ind];

if(ind==0) return arr[ind];
if(ind<0)  return 0;

int pick= arr[ind]+ solveUtil(ind-2, arr,dp);
int nonPick = 0 + solveUtil(ind-1, arr, dp);

return dp[ind]=max(pick, nonPick);
  }

int rob(vector<int>& nums) {
    int n = nums.size();
  vector<int> dp(n,-1);
return solveUtil(n-1,nums, dp);
}
};

C++11 lambda: when do we need to capture [*this] instead of [this]?

For a quick sample we could see:

class Foo {
  std::string s_; 
  int i_;
public:
  Foo(const std::string& s, int i) : s_(s), i_(i) {}
  void Print() {
    auto do_print = [this](){
      std::cout << s_ << std::endl;
      std::cout << i_ << std::endl;
    };
    do_print();
  }
};

Ok, capture [this], so the lambda do_print could use s_ and i_ member of Foo. Capture [this] is enough.

But when do we have to capture [*this] instand of [this]? What could be a typical use-case / scenario, any quick samples?

Thanks!

How to initialize std::vector with std::array [closed]

codes like this can not compiled with Clang 13.1.6:

std::vector<std::array<std::string, 3>> params{
    {"1", "a", "a"},
    {"2", "b", "b"},
    {"3", "c", "c"}
};

tried also:

std::vector<std::array<std::string, 3>> params{
    ,
    ,
    
};

why? and how to make initialize works?

dimanche 31 juillet 2022

Timer thread implementation

I've implemented a timer thread whose job is to call functions at a certain interval. The functions may be called multiple times if their interval > 0 millisec otherwise only one time. Even if the timer thread is running, still a new function can be registered. Could you please provide your feedback and improvement factors?

class TimerThread
{
    using ClockType = std::chrono::high_resolution_clock;

public:
    TimerThread() = default;
    TimerThread(TimerThread const &) = delete;
    TimerThread & operator=(TimerThread const &) = delete;

    ~TimerThread() noexcept
    {
        try
        {
            stop();
        }
        catch(std::exception const &ex)
        {
            std::cout << "Exception: " << ex.what() << std::endl;
        }
        catch(...)
        {
        }
    }

    void registerCallback(std::function<void()> func, uint32_t const interval=0)
    {
        std::unique_lock<std::mutex> lock{mt_};
        timers_.emplace_back(std::move(func), ClockType::now(), interval);
        cv_.notify_all();
    }

    void start(uint32_t const delay=0)
    {
        if (! start_)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(delay));
            workerThread_ = std::thread{&TimerThread::run, this};
            start_ = true;
        }
    }

private:
    void run()
    {
        for (auto &t: timers_)
            t.prevFireTime_ = ClockType::now();

        while (startTimerFlag_.load(std::memory_order_acquire))
        {
            std::unique_lock<std::mutex> lock{mt_};
            cv_.wait(lock, [this]() -> bool {
                return ! timers_.empty();
            });

            for (auto &t: timers_)
                if (t.isReady())
                    t.func_();
        }
    }

    void stop()
    {
        startTimerFlag_.store(false, std::memory_order_release);
        cv_.notify_all();
        if (workerThread_.joinable())
            workerThread_.join();
    }

    struct TimerInfo
    {
        TimerInfo() = default;

        TimerInfo(std::function<void()> func, ClockType::time_point prevFireTime, uint32_t const interval):
            func_{std::move(func)},
            prevFireTime_{prevFireTime},
            intervalMilliSec_{interval}
        {
        }

        bool isReady()
        {
            if (!isFiredFirstTime)
            {
                isFiredFirstTime = true;
                return true;
            }
            else if (intervalMilliSec_ != 0)
            {
                auto current = ClockType::now();
                uint32_t const duration = std::chrono::duration_cast<std::chrono::milliseconds>(current - prevFireTime_).count();

                if (duration >= intervalMilliSec_)
                {
                    prevFireTime_ = current;
                    return true;
                }
            }

            return false;
        }

        std::function<void()> func_;
        ClockType::time_point prevFireTime_;
        uint32_t intervalMilliSec_;
        bool isFiredFirstTime{false};
    };

    std::vector<TimerInfo> timers_;
    std::thread workerThread_;
    std::mutex mt_;
    std::condition_variable cv_;
    std::atomic<bool> startTimerFlag_{true};
    bool start_{false};
};

int main()
{
    TimerThread timer;

    timer.registerCallback([](){
        std::cout << "Timer 1 - " << std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() << std::endl;
    }, 1000);

    timer.registerCallback([](){
        std::cout << "Timer 2 - " << std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() << std::endl;
    }, 2000);

    timer.registerCallback([](){
        std::cout << "Timer 3 - " << std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() << std::endl;
    });

    timer.start();

    std::this_thread::sleep_for(std::chrono::seconds(5));

    LOG("Terminating main()...");

    return 0;
}