Home > Software engineering >  Syntax options for accepting capturing lambdas as constructor args and storing them in class
Syntax options for accepting capturing lambdas as constructor args and storing them in class

Time:01-28

I am trying to write a vector wrapper (indexed_vec) which stores objects of type ValueType but other datastructures (vectors of other types) refer to these by index (because iterators are clearly not stable).

So when objects of ValueType are deleted in indexed_vec, some housekeeping has to be done to keep the indeces in the other other datastructures up to date.

Therefore indexed_vec also stores 2 lambda's which are effectively "subscribers" (Observer Pattern) to index changes.

The code below works, as desired, but the syntax for constructing the instance of indexed_vec seems clumsy.

Are there better alternatives? I investigated deduction guides, but that doesn't work apparently.

A factory function?

(NOTE: the wrappers exposing of the inner vector as a public members will not stay like that, it's just to reduce the code here)


#include <cstddef>
#include <iostream>
#include <vector>

template <typename ValueType, typename DeleteIndexCBType, typename ChangeIndexCBType>
struct indexed_vec {
    indexed_vec(DeleteIndexCBType& dcb_, ChangeIndexCBType& ccb_) : dcb(dcb_), ccb(ccb_) {}

    DeleteIndexCBType dcb;
    ChangeIndexCBType ccb;

    std::vector<ValueType> v;

    std::size_t erase(std::size_t index_to_erase) {
        // TODO handle empty vector
        v[index_to_erase] = v.back();
        v.pop_back();
        dcb(index_to_erase);
        ccb(v.size(), index_to_erase); // NOTE v.size() is NOW one off the end, but that's accurate
        return 1;
    }
};

template <typename T>
void print(const std::vector<T>& v) {
  for (auto& e: v) std::cout << e << " ";
  std::cout << '\n';
}
  

int main() {
    std::string context = "captured context";

    auto delete_subscriber = [&context](std::size_t idx) {
        std::cout << "deleter: " << context << ": " << idx << "\n";
    };
    auto change_subscriber = [&context](std::size_t old_idx, std::size_t new_idx) {
        std::cout << "updater: " << context << ": " << old_idx << " => " << new_idx << "\n";
    };

    // this seems clumsy?
    indexed_vec<std::size_t, decltype(delete_subscriber), decltype(change_subscriber)> v1(
        delete_subscriber, change_subscriber);

    v1.v.reserve(10);
    for (std::size_t v = 10; v != 20;   v) v1.v.push_back(v);

    print(v1.v);
    v1.erase(3);
    print(v1.v);
}

CodePudding user response:

One easy easy way to simplify the syntax is to wrap the verbose code in a factory function like

template <typename ValueType, typename DeleteIndexCBType, typename ChangeIndexCBType>
auto make_index_vector(const DeleteIndexCBType& dcb_, const ChangeIndexCBType& ccb_)
{
    return indexed_vec<ValueType, DeleteIndexCBType, ChangeIndexCBType>(dcb_, ccb_);
}

Using that allows you to declare v1 like

auto v1 = make_index_vector<std::size_t>(delete_subscriber, change_subscriber);

CodePudding user response:

Since you can't supply only one template parameter out of three, a normal deduction guide won't work, but you could package ValueType in a tag.

template <class T> struct ValueTag {};

template <class ValueType, class DeleteIndexCBType, class ChangeIndexCBType>
struct indexed_vec {
    // added constructor for tag:
    indexed_vec(ValueTag<ValueType>, DeleteIndexCBType& dcb_,
                ChangeIndexCBType& ccb_) :
        dcb(dcb_), ccb(ccb_) {}

    //...

Deduction guide:

template<class ValueType, class DeleteIndexCBType, class ChangeIndexCBType>
indexed_vec(ValueTag<ValueType>, DeleteIndexCBType, ChangeIndexCBType) ->
    indexed_vec<ValueType, DeleteIndexCBType, ChangeIndexCBType>;

Usage:

indexed_vec v1(ValueTag<size_t>{}, delete_subscriber, change_subscriber);
  • Related