c++ - How to return a variant from an input iterator with high performance? -
i have file format decoder returns custom input iterator. value type of iterator (when dereferencing *iter
) can 1 of many token types.
here's simplified usage example:
file file {"/path/to/file"}; (const auto& token : file) { // token }
how can token
have multiple possible types? depending on type of token, type of payload changes too.
performance important here during traversal. don't want unnecessary allocation, example. why iterator's type input iterator: advance iterator, previous token invalidated per requirements of inputiterator
tag.
i have 2 ideas in mind far:
use single
token
class privateunion
of possible payloads (with public getters) , public type id (enum
) getter. user needs switch on type id know payload getter call:for (const auto& token : file) { switch (token.type()) { case token::type::apple: const auto& apple = token.apple(); // ... break; case token::type::banana: const auto& banana = token.banana(); // ... break; // ... } }
although choose in c, i'm not fan of solution in c++ because user can still call wrong getter , nothing can enforce (except run-time checks want avoid performance concerns).
create abstract
token
base class hasaccept()
method accept visitor, , multiple concrete classes (one each payload type) inheriting base class. in iterator object, instantiate 1 of each concrete class @ creation time. havetoken *token
member. when iterating, fill appropriate pre-allocated payload object, , setthis->token = this->specifictoken
. makeoperator*()
returnthis->token
(reference to). ask user use visitor during iteration (or worse, usedynamic_cast
):class myvisitor : public tokenvisitor { public: void visit(const appletoken& token) override { // ... } void visit(const bananatoken& token) override { // ... } }; tokenvisitor visitor; (const auto& token : file) { token.accept(visitor); }
this introduces additional function calls each token, @ least 1 of them virtual, might not end of world; remain open solution.
is there other interesting solution? consider returning boost::variant
or std::variant
same idea #2.
although choose in c, i'm not fan of solution in c++ because user can still call wrong getter , nothing can enforce (except run-time checks want avoid performance concerns).
you can reverse approach , accept callable object instead of returning iterator user. can iterate container internally , dispatch right type. way users cannot mistakes anymore ignoring information carried tagged union, in charge of taking in consideration.
here minimal, working example show mean:
#include <vector> #include <utility> #include <iostream> struct {}; struct b {}; class c { struct s { enum { a_tag, b_tag } tag; union { a; b b; }; }; public: void add(a a) { s s; s.a = a; s.tag = s::a_tag; vec.push_back(s); } void add(b b) { s s; s.b = b; s.tag = s::b_tag; vec.push_back(s); } template<typename f> void iterate(f &&f) { for(auto &&s: vec) { if(s.tag == s::a_tag) { std::forward<f>(f)(s.a); } else { std::forward<f>(f)(s.b); } } } private: std::vector<s> vec; }; void f(const &) { std::cout << "a" << std::endl; } void f(const b &) { std::cout << "b" << std::endl; } int main() { c c; c.add(a{}); c.add(b{}); c.add(a{}); c.iterate([](auto item) { f(item); }); }
see , running on coliru.
wiki
Comments
Post a Comment