Dynamic polymorphism using std :: variant and std :: visit

Hello, Khabrovites. As part of the course "C ++ Developer. Professional" we have prepared a translation of the material for you.



We also invite you to an open webinar on
"Scopes of Visibility and Invisibility". Participants, together with an expert, will implement a general-purpose class for an hour and a half lesson and try to run several unit tests using googletest.






Dynamic polymorphism (or runtime polymorphism) is usually associated with v-tables and virtual functions. However, in this article, I will show you a modern C ++ technique that uses std::variant



and std::visit



. This C ++ 17 technique can offer you not only better performance and value semantics, but also interesting design patterns.





: 2 2020 . ( , , ).





, , , .





, . ​​ , .   (v-). , , , -. v- .





:





class Base {
public:
    virtual ~Base() = default;

    virtual void PrintName() const { 
        std::cout << "calling Bases!\n"
    }
};

class Derived : public Base {
public:
    void PrintName() const override { 
        std::cout << "calling Derived!\n"
    }
};

class ExtraDerived : public Base {
public:
    void PrintName() const override { 
        std::cout << "calling ExtraDerived!\n"
    }
};

std::unique_ptr<Base> pObject = std::make_unique<Derived>();
pObject->PrintName();
      
      



? :





  • , .





  • , , .





  • - — .





  • , .





  • .





«». , . , . . .





?





  • , ( , ).





  • , , .





  • , .





C++17 ( , boost) ! .





std::variant std::visit

std::variant



, C++17, . «» std::variant



.





Base



, : 





-, :





class Derived {
public:
    void PrintName() const { 
        std::cout << "calling Derived!\n"
    }
};

class ExtraDerived {
public:
    void PrintName() const { 
        std::cout << "calling ExtraDerived!\n"
    }
};
      
      



, ! .





:





std::variant<Derived, ExtraDerived> var;
      
      



var



, Derived ExtraDerived. . variant : , std::variant C ++ 17.





PrintName()



, var?





: std::visit



.





struct CallPrintName {
    void operator()(const Derived& d) { d.PrintName(); }    
    void operator()(const ExtraDerived& ed) { ed.PrintName(); }    
};

std::visit(CallPrintName{}, var);
      
      



, . std::visit



.





, (visitor) :





auto caller = [](const auto& obj) { obj.PrintName(); }
std::visit(caller, var);
      
      



«» … , ?





, :





void PrintName(std::string_view intro) const { 
    std::cout << intro << " calling Derived!\n;
}
      
      



- . , std::visit()



. - std::variant



( ).





— - .





struct CallPrintName {
    void operator()(const Derived& d) { d.PrintName(intro); }    
    void operator()(const ExtraDerived& ed) { ed.PrintName(intro); } 

    std::string_view intro;
};

std::visit(CallPrintName{"intro text"}, var);
      
      



(visitor) -, -:





auto caller = [&intro](const auto& obj) { obj.PrintName(intro); }
std::visit(caller, var);
      
      



. ?





std::variant 

  • ,





  • «», .





  • ,





  • (Duck typing): , , (visitor). , . . .





std::variant

  • , . , . , variant .





  • , std::variant



    . , 10 , — 100 , 100 . , 90 .





  • : , , , .





  • . .





  • , , std::visit



    .





, - .





, (Label) . SimpleLabel



- , DateLabel



, , IconLabel



, .





, HTML-, :





class ILabel {
public:
    virtual ~ILabel() = default;

    [[nodiscard]] virtual std::string BuildHTML() const = 0;
};

class SimpleLabel : public ILabel {
public:
    SimpleLabel(std::string str) : _str(std::move(str)) { }

    [[nodiscard]] std::string BuildHTML() const override {
        return "<p>" + _str + "</p>";
    }

private:
    std::string _str;    
};

class DateLabel : public ILabel {
public:
    DateLabel(std::string dateStr) : _str(std::move(dateStr)) { }

    [[nodiscard]] std::string BuildHTML() const override {
        return "<p class=\"date\">Date: " + _str + "</p>";
    }

private:
    std::string _str;    
};

class IconLabel : public ILabel {
public:
    IconLabel(std::string str, std::string iconSrc) : 
         _str(std::move(str)), _iconSrc(std::move(iconSrc)) { }

    [[nodiscard]] std::string BuildHTML() const override {
        return "<p><img src=\"" + _iconSrc + "\"/>" + _str + "</p>";
    }

private:
    std::string _str;    
    std::string _iconSrc;
};
      
      



ILabel



, , - BuildHTML.





, ILabel HTML-:





std::vector<std::unique_ptr<ILabel>> vecLabels;
vecLabels.emplace_back(std::make_unique<SimpleLabel>("Hello World"));
vecLabels.emplace_back(std::make_unique<DateLabel>("10th August 2020"));
vecLabels.emplace_back(std::make_unique<IconLabel>("Error", "error.png"));

std::string finalHTML;
for (auto &label : vecLabels)
    finalHTML += label->BuildHTML() + '\n';

std::cout << finalHTML;
      
      



, BuildHTML , :





<p>Hello World</p>
<p class="date">Date: 10th August 2020</p>
<p><img src="error.png"/>Error</p>
      
      



std::variant



:





struct VSimpleLabel {
    std::string _str;    
};

struct VDateLabel {
    std::string _str;    
};

struct VIconLabel {
    std::string _str;    
    std::string _iconSrc;
};

struct HTMLLabelBuilder {
    [[nodiscard]] std::string operator()(const VSimpleLabel& label) {
        return "<p>" + label._str + "</p>";
    }
    [[nodiscard]] std::string operator()(const VDateLabel& label) {
        return "<p class=\"date\">Date: " + label._str + "</p>";
    }
    [[nodiscard]] std::string operator()(const VIconLabel& label) {
        return "<p><img src=\"" + label._iconSrc + "\"/>" + label._str + "</p>";
    }
};
      
      



Label



. , HTML- HTMLLabelBuilder



.





:





using LabelVariant = std::variant<VSimpleLabel, VDateLabel, VIconLabel>;
std::vector<LabelVariant> vecLabels;
vecLabels.emplace_back(VSimpleLabel { "Hello World"});
vecLabels.emplace_back(VDateLabel { "10th August 2020"});
vecLabels.emplace_back(VIconLabel { "Error", "error.png"});

std::string finalHTML;
for (auto &label : vecLabels)
    finalHTML += std::visit(HTMLLabelBuilder{}, label) + '\n';

std::cout << finalHTML;
      
      



Coliru.





HTMLLabelBuilder



— , . , - :





struct VSimpleLabel {
    [[nodiscard]] std::string BuildHTML() const {
        return "<p class=\"date\">Date: " + _str + "</p>";
    }

    std::string _str;    
};

struct VDateLabel {
    [[nodiscard]] std::string BuildHTML() const {
        return "<p class=\"date\">Date: " + _str + "</p>";
    }

    std::string _str;    
};

struct VIconLabel {
    [[nodiscard]] std::string BuildHTML() const {
        return "<p><img src=\"" + _iconSrc + "\"/>" + _str + "</p>";
    }

    std::string _str;    
    std::string _iconSrc;
};

auto callBuildHTML = [](auto& label) { return label.BuildHTML(); };
for (auto &label : vecLabels)
    finalHTML += std::visit(callBuildHTML, label) + '\n'
      
      



, , .





(Concepts)

std::variant/std::visit



, . , . , C++20 , , .





( Mariusz J )





template <typename T>
concept ILabel = requires(const T v)
{
    {v.buildHtml()} -> std::convertible_to<std::string>;
};
      
      



, - buildHtml()



, , std::string



.





( constrained auto



):





auto callBuildHTML = [](ILabel auto& label) -> std::string { return label.buildHtml(); };
for (auto &label : vecLabels)
    finalHTML += std::visit(callBuildHTML, label) + '\n';
      
      



@Wandbox.





, std::variant



.





:





unique_ptr std::variant C++17 -





, , , , .





std::visit



, ?





.





ILabel



, .





@QuickBench.





, ; , , .





, .





using ABC = std::variant<AParticle, BParticle, CParticle>;
std::vector<ABC> particles(PARTICLE_COUNT);

for (std::size_t i = 0; auto& p : particles) {
  switch (i%3) {
    case 0: p = AParticle(); break;
    case 1: p = BParticle(); break;
    case 2: p = CParticle(); break;
  }
  ++i;
}

auto CallGenerate = [](auto& p) { p.generate(); };
for (auto _ : state) {
  for (auto& p : particles)
    std::visit(CallGenerate, p);
}
      
      



Particle



( AParticle, BParticle . .) 72 , Generate()



, «».





10% std::visit



!





? , :





  • variant , . .





  • , , , , v-.





, , variant 20% , : td::vector particles(PARTICLE_COUNT);



. QuickBench





, std::visit



. , TCPIP std::visit



. , .





CppCon 2018: « std::variant»





, - std::visit



. , , .





, :





  • V2 –





  • - std::variant? std::visit fault : r/cpp





  • - std::variant? std::visit fault (Part 2) : r/cpp





, std::variant



, , «» . , , .





std::visit std::variant

, , .





@BuildBench





: GCC 10.1, C++17, O2:





! , — 39k 44k. , 2790 LOC 1945 LOC .





.





, C++, .





. std::variant



, — , . std::visit



, , .





std::variant



«» ? , . , std::variant



, , - . , , std::variant



, .





, . . ( Github):





std::variant/std::visit . , - , . , , . / , , . variant ( , ) .





, , , ( , move ), , , - . , . , , - . , callback hell. , , .





:





  • std::variant



    std::visit



    ?





  • ?





.






"C++ Developer. Professional".





« ».








All Articles