Почему std:: visit принимает переменное количество вариантов?

Попытка познакомиться с С++ 17, я только что заметил std::visit:

template <class Visitor, class... Variants>
constexpr /*something*/ visit(Visitor&& vis, Variants&&... vars);

Почему std::visit не брать ни одного варианта, а сколько угодно вариантов? Я имею в виду, что вы всегда можете использовать некоторую стандартную библиотечную функцию и иметь несколько параметров с одинаковой ролью, работая над всеми из них (например, std::find() для нескольких элементов в контейнере); или вы можете брать нескольких посетителей и использовать их в одном и том же варианте.

Итак, почему эта конкретная "вариация"?

Ответы

Ответ 1

Потому что нам нужно разрешить посещать комбинации классов в вариантах. То есть, если мы имеем

using Var1 = std::variant<A,B>;
using Var2 = std::variant<C,D>;

мы можем, очевидно, использовать таких посетителей:

struct Visitor1 {
    void operator()(A);
    void operator()(B);
};

struct Visitor2 {
    void operator()(C);
    void operator()(D);
};

с Var1 и Var2 соответственно. Мы можем использовать этот следующий вид, как с Var1, так и Var2:

struct Visitor3 {
    void operator()(A);
    void operator()(B);
    void operator()(C);
    void operator()(D);
};

но какой OP отсутствует, мы хотим, чтобы вы могли посетить одну из четырех пар (A,C), (A,D), (B,C), (B,D) - при просмотре пары Var1 и Var2 вместе. Поэтому вариационный аргумент std::visit является все-таки необходимым. Соответствующий посетитель будет выглядеть так:

struct Visitor4 {
    void operator()(A,C);
    void operator()(A,D);
    void operator()(B,C);
    void operator()(B,D);
};

и мы будем называть std::visit(Visitor4{}, my_var1_instance, my_var2_instance);

Я понял это при чтении ответа Барри.

Ответ 2

Сделать несколько посещений более чистыми. Скажем, у меня было два std::variant<A,B>, один с именем left и один с именем right. При множественном посещении я могу написать:

struct Visitor {
    void operator()(A, A);
    void operator()(A, B);
    void operator()(B, A);
    void operator()(B, B);
};

std::visit(Visitor{}, left, right);

Это довольно чистый интерфейс, и это довольно часто полезно. Он также легко реализуется эффективно - вы просто создаете n-мерный массив функций вместо одномерного массива.

С другой стороны, при единственном посещении вам нужно будет написать:

std::visit([&](auto l_elem){
    std::visit([&](auto r_elem){
        Visitor{}(l_elem, r_elem);
    }, right)
}, left);

Это жалко писать, жалко читать и, вероятно, менее эффективно.