0

Consider the following code (run it)

#include <fmt/core.h>
#include <string>
#include <array>
#include <variant>

struct Student {
    std::string name;
    Student(const char* name) : name(name) {}
    void display() { fmt::print("Student {}\n", name); }
    virtual play() { fmt::print("Student {} played\n", name); }
};

struct Teacher {
    std::string name;
    Teacher(const char* name) : name(name) {}
    void display() { fmt::print("Teacher {}\n", name); }
    virtual play() { fmt::print("Teacher {} played\n", name); }
};

int main() {
    // case 1: use Student
    Student student{"Alice"};
    student.display();

    // case 2: use std::variant which holds a Student
    std::variant<Student, Teacher> person(std::in_place_type<Student>, "Charlie");
    std::visit([](auto&& arg) {
        arg.display();
    }, person);
}

What is the cost of case 2 comparing to case 1?

Does it only need to identify the type (using some saved index) and then do a reinterpret_cast like operation and then invoke the display member function?

Another question as raised by @alfC: if I change the above main to call play instead of display, which is a virtual function, will the cost be different?

doraemon
  • 2,296
  • 1
  • 17
  • 36
  • This is *not* a fair comparison. It would make more sense to compare it the call of a virtual function call. See https://www.youtube.com/watch?v=4eeESJQk-mw – alfC Mar 25 '23 at 16:14
  • 1
    This is comparing two unrelated things. – user3840170 Mar 25 '23 at 16:21
  • @user3840170 why they are complete unrelated? I need to know what std::visit does under the scene before the function call is invoked – doraemon Mar 25 '23 at 16:24
  • @alfC Thanks for pointing out the virtual function call. I edited the PO. Thanks also for recommending the video. I will take a look – doraemon Mar 25 '23 at 16:25
  • Because `std::variant` and `std::array` are not the same data structure. There is even a certain formal sense in which they are almost diametrically opposed. – user3840170 Mar 25 '23 at 16:28
  • If I change the case 1 to a simple `Student student{"Alice"}`, will this make the comparison be more reasonable – doraemon Mar 25 '23 at 16:30
  • 1
    _what is the cost of using std::visit_ To answer the title of the question, probably much the same as a `switch` statement, just neater. – Paul Sanders Mar 25 '23 at 16:30
  • @PaulSanders you answered to the point that I am keen to know. Does it involves something like this: `switch (person.index) {case 0: reinterpret_cast(internal_data).display(); break; ...` If yes, could you please post as an answer? – doraemon Mar 25 '23 at 16:35
  • I would expect it to, yes. [Godbolt](https://godbolt.org/) is good for answering questions like this because you can inspect the code emitted by the compiler. – Paul Sanders Mar 25 '23 at 16:38
  • I cannot quite read assembly code. Could you please point to which part corresponds to the switch? – doraemon Mar 25 '23 at 16:39
  • 1
    Godbolt highlights source lines and the corresponding assembly code in the same colour, does that help? – Paul Sanders Mar 25 '23 at 16:40

1 Answers1

2

From std::visit

Complexity
When the number of variants is zero or one, the invocation of the callable object is implemented in constant time, i.e. it does not depend on sizeof...(Types).

So it forbids if-chain and force something similar to a virtual call.

If the number of variants is larger than 1, the invocation of the callable object has no complexity requirements.

So depend more about implementation: might be a layer by variant, or a bigger layer to handle the combination.

As-if rule might allow optimization such as de-virtualization.

And as always with performance, you have to measure for your specific cases.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks. Could you please elaborate a litle on how gcc or/and clang handles it? Is it something like a `switch` as pointed out by Paul Sanders in the comment? – doraemon Mar 26 '23 at 00:08