Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

What is the approach to handle optional class members?

Processing large amounts of data (gigabytes) I use indexes to data arrays. Since access to data could lead to cache inefficiency, I want to cache some data from array together with the index which gives dramatic speedup for operations through indexes.

The amount of cached data is compile-time choice which should include zero amount of cache data. I have large amount of indexes, so in this case I don’t want to pay for extra “empty” element like std::array does, for example.

So, I made a template with a specialization:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

using index_t = unsigned int;
using lexem_t = unsigned int;

template <std::size_t t_arg_cache_line_size>
struct lexem_index_with_cache_t {
    index_t index;
    std::array<lexem_t, t_arg_cache_line_size> cache_line;

    constexpr std::size_t cache_line_size() const {
        return t_arg_cache_line_size;
    }
};

template<>
struct lexem_index_with_cache_t<0> {
    index_t index;
    static std::array<lexem_t, 0> cache_line;

    constexpr std::size_t cache_line_size() const {
        return 0;
    }
};

std::array<lexem_t, 0> lexem_index_with_cache_t<0>::cache_line;

The problem is this “hack” I used in the specialization with zero size which utilizes static member to give formal access to the cache_line while is it empty and the access is not really needed. This allows me to avoid specializations in functions which use this template, like here:

using lexem_index_with_cache = lexem_index_with_cache_t<0>;

template <typename T>
class seq_forward_comparator_cached
{
    const std::vector<T>& vec;
public:
    seq_forward_comparator_cached(const std::vector<T>& vec) : vec(vec) { }

    bool operator() (const lexem_index_with_cache& idx1, const lexem_index_with_cache& idx2)
    {
        if (idx1.index == idx2.index) {
            return false;
        }

        const auto it1_cache_line = idx1.cache_line;  // This code wouldn’t compile in absence of static “hack”
        const auto it2_cache_line = idx2.cache_line;  // This code wouldn’t compile in absence of static “hack”

        auto res = std::lexicographical_compare_three_way(
            it1_cache_line.begin(), it1_cache_line.end(),
            it2_cache_line.begin(), it2_cache_line.end());

        if (res == std::strong_ordering::equal) {
            auto range1 = std::ranges::subrange(vec.begin() + idx1.index + idx1.cache_line_size(), vec.end());
            auto range2 = std::ranges::subrange(vec.begin() + idx2.index + idx2.cache_line_size(), vec.end());

            return std::ranges::lexicographical_compare(range1, range2);
        }

        return res == std::strong_ordering::less;
    }
};

Of course, I can implement another template specialization of this template for zero size cache, but this will lead to code duplication and I have many such functions, so I don’t want to specialize all of them.

What is a proper way in modern C++ to avoid this static hack and possible code duplication on the other hand?

I am not sure, maybe some kind of conditional code include depending on the type could help.

I would like to avoid wrapping access to cache_line to a function, but if this is the only case, please give a clue on the approach.

The compilable code is here.

>Solution :

I’ve added an example with a trick to use the if constexpr(...) in non-template code.

It is now impossible to actually use the at() function accidentally, as opposed to the static member solution.

#include <array>

using data_type = int;

template<size_t _data_size>
class ExtendableIndex
{
public:
    constexpr static size_t data_size = _data_size;

    data_type& at(size_t idx) { return data[idx]; }

    size_t index;
    std::array<data_type, _data_size> data;
};

template<>
class ExtendableIndex<0>
{
public:
    constexpr static size_t data_size = 0;

    data_type& at(size_t idx);

    size_t index;
};

using DefaultIndex = ExtendableIndex<0>;

class DataUser
{
public:

    void process(DefaultIndex& index)
    {
        if constexpr (DefaultIndex::data_size > 0)
        {
            // auto value = index.data[0]; // -> this fails to compile
            auto value = index.at(0); // -> but this slight workaround solves the issue, `at()` is not implemented and thats OK.
        }
    }

    template<size_t _data_size>
    void process_template(ExtendableIndex<_data_size>& index)
    {
        if constexpr (DefaultIndex::data_size > 0)
        {
            auto value = index.data[0]; // -> this compiles even if index.data doesn't exist when 'process' is a template
        }
    }

};

int main()
{
    DataUser r;
    ExtendableIndex<0> index_zero;

    r.process(index_zero);
    r.process_template(index_zero);

    ExtendableIndex<1> index_one;
    r.process_template(index_one);
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading