swift - Is it possible to use template arguments in virtual function in modern C++? -


i used c++ development several years ago , found difficult combine template programming oop. program in swift , tried doing of things struggled then.

this swift code illustrate problem:

// protocol java interface or c++ pure virtual base class protocol log {      // want able add elements collection of ints,     // should sort of collection      // can treated sequence     func add<t: sequencetype t.generator.element == int>(values: t) }  class discretelog: log {     var vals: [int] = []     func add<t: sequencetype t.generator.element == int>(values: t) {         v in values {             vals.append(v)         }     }  }  class continouslog: log {     var vals: [double] = []      func add<t: sequencetype t.generator.element == int>(values: t) {         v in values {             vals.append(double(v))         }     } }  // don't have know whether log continuous or discrete // can still add elements var log: log = continouslog() log.add([1, 2, 3])  // , elements can come kind of sequence, not need // array log.add(["four": 4, "five: 5].values) 

so problem if c++ code defined as:

virtual void add(vector<int> elements>) 

then sure have multiple subclasses implement method, never provide vectors arguments.

i try changing more generic using iterator:

virtual void add(vector<int>::iterator elements>) 

but still limited using vector iterators. guess have write like:

template<typename iterator> virtual void add(iterator elements>) 

but give compile errors template based arguments not allowed virtual methods.

anyway wondered if sort of thing possible in modern c++.

c++ templates , c#/swift/java generics different things.

they both "pattern code" in sense (they patterns generate code), c#/swift/java generics use type erasure , "forget" types work with, while c++ templates elephants. , elephants never forget.

it turns out can make elephant forget, have tell to. technique of "forgetting" details of type known "type erasure" or "run time concepts".

so want type erase down concept of "a sequence of integers". want take type, long sequence of integers, , able iterate on it. seems fair.

boost has such type erasures. wants rely on boost?

first, type erase input iterator:

  template<class t>   struct input_iterator:     std::iterator<       std::input_iterator_tag, // category       t, // value       std::ptrdiff_t, // distance       t*, // pointer       t // reference     >   {     struct erase {       virtual void advance() = 0;       virtual erase* clone() const = 0;       virtual t get() const = 0;       virtual bool equal(erase const& o) = 0;       virtual ~erase() {}     };     std::unique_ptr<erase> pimpl;     input_iterator(input_iterator&&)=default;     input_iterator& operator=(input_iterator&&)=default;     input_iterator()=default;     input_iterator& operator++() {       pimpl->advance();       return *this;     }     input_iterator operator++(int) {       auto copy = *this;       ++*this;       return copy;     }     input_iterator(input_iterator const& o):       pimpl(o.pimpl?o.pimpl->clone():nullptr)     {}     input_iterator& operator=(input_iterator const&o) {       if (!o.pimpl) {         if (pimpl) pimpl->reset();         return *this;       }       pimpl = std::unique_ptr<erase>(o.pimpl->clone());       return *this;     }     t operator*() const {       return pimpl->get();     }     friend bool operator==( input_iterator const& lhs, input_iterator const& rhs ) {       return lhs.pimpl->equal(*rhs.pimpl);     }     friend bool operator!=( input_iterator const& lhs, input_iterator const& rhs ) {       return !(lhs==rhs);     }     template<class it>     struct impl:erase{       it;       impl(impl const&)=default;       impl(it in):it(std::move(in)){}       virtual void advance() override { ++it; }       virtual erase* clone() const override { return new impl(*this); }       virtual t get() const override { return *it; }       virtual bool equal(erase const& o) override {         return static_cast<impl const&>(o).it == it;       }     };      template<       class it,       class=std::enable_if<         std::is_convertible<           typename std::iterator_traits<it>::reference,           t         >{}       >     >     input_iterator(it it):pimpl( new impl<it>{it} ) {}   }; // input_iterator 

next, have range template. container stores non-type erased iterators, , exposes enough iterate on iterators.

 template<class it>  struct range {    b; e;    begin() const { return b; }    end() const { return e; }     range() = default;     range(it start, finish):b(std::move(start)),e(std::move(finish)) {};     range(range&&)=default;    range(range const&)=default;    range& operator=(range&&)=default;    range& operator=(range const&)=default;     template<class r,      class r_it=std::decay_t<decltype(std::begin(std::declval<r>()))>,      class=std::enable_if< std::is_convertible<r_it, it>{} >    >    range( r&& r ):      range(std::begin(r), std::end(r))    {} // todo: enable adl begin lookup  }; 

the above type basic: c++1z has better ones, boost, have in own code base. enough handle for(:) loops, , implicit conversion containers compatible iterators.

finally our sequence type:

template<class t> using sequence_of = range<input_iterator<t>>; 

wait, that's it? nice, types compose well!

and barring errors, done.

your code take sequence_of<int>, , pass std::vector<int> or std::list<int> or whatever.

the input_iterator type-erasure type-erases iterator down getting t via *, ==, copy, , ++ advance, enough for(:) loop.

the range<input_iterator<int>> accept iterable range (including containers) iterators can converted input_iterator<int>.

the downside? introduced bunch of overhead. each method goes through virtual dispatch, ++ * ==.

this (roughly) generics -- type-erase down requirements give in generic clause. means working abstract objects, not concrete objects, unavoidably suffer performance penalties of indirection.

c++ templates can used generate type erasure, , there tools (boost has some) make easier. did above half-assed manual one. similar techniques used in std::function<r(args...)>, type-erases down (conceptually) {copy, call (args...) returning r, destroy} (plus incidentals).

live example.

(the code above freely uses c++14.)

so c++ equivalent log is:

struct log {   virtual void add(sequence_of<int>) = 0;   virtual ~log() {} }; 

now, type erasure code above bit ugly. fair, implemented language feature in c++ without direct language support it.

i've seen proposals make type erasure easier in c++. not know status of proposals.


if want own, here "easy" way type erasure in 3 steps:

first, determine operations want erase. write equivalent of input_iterator<t> -- give bunch of methods , operators want. sparse. call "external type". ideally nothing in type virtual, , should regular or semi-regular type (ie, should behave value-like, or move-only-value-like). don't implement interface yet.

second, write inner class erase. provides pure-virtual interface set of functions provide need in external type.

store unique_ptr<erase> pimpl; within external type. forward methods expose in external type pimpl;.

third, write inner template<class x> class impl<x>:erase. stores variable x x;, , implements in erase interacting x. should constructable x (with optional perfect forwarding).

you create perfect forwarding constructor external type creates pimpl via new impl<x>(whatever). ideally should check argument valid 1 via sfinae techniques, qualify of implementation issue.

now external type "erases" type of object constructed "down to" operations exposed.


now, actual problem, i'd write array_view or steal std::experimental::array_view, , restrict input kind of contiguous buffer of data of type. more performant, , accepting sequence on engineering unless need it.


Comments

Popular posts from this blog

PHP DOM loadHTML() method unusual warning -

python - How to create jsonb index using GIN on SQLAlchemy? -

c# - TransactionScope not rolling back although no complete() is called -