ARTICLE AD BOX
Here is another proposal that might be more idiomatic than using std::variant::index. In order to make the following more interesting, we will add another type to the variant, i.e. std::variant<int, std::string, double>.
Actually, when using std::variant, the idiomatic way to recover the actual type of the variant value is to use std::variant::visit. By doing so, you have to provide one visitor whose parameter can handle each type of the variant. For instance, we could write (see here for overloads definition)
auto visitor = overloads { [&](int) { std::get<int> (tup) = 1; }, [&](std::string s) { std::get<std::string>(tup) = "2"; }, [&](double) { std::get<double> (tup) = 3.0; } }; std::visit (visitor, this->flavours[index]);Note how we use here the possibility for std::get to use a type as template parameter instead of an integer index (possible when the tuple holds distinct types).
Unfortunately, this wouldn't compile in case AllArgs... holds only one type , say int for instance. In such a case, the visitor object (that must provide a case for each possible type) would try to access the tuple item for type double for instance, item that would not exist in that case.
In order to handle this, we begin by defining our visitor by
std::visit ([&] (auto const& e) { (*this) (sausage_roll, order, e); }, this->flavours[index]);In other words, our visitor takes an auto object as input and forwards the actual work to a functor; note that this functor takes sausage_roll, order and e (that can be of type int, std::string or double).
Now the problem becomes how to define the required overloads of this functor. First, we have to provide in Greggs an implementation that will do nothing when the type will not be one of the type in AllArgs.... This can be done with some SFINAE and a fold expression:
template<typename T, std::enable_if_t<(not std::is_same_v<T,AllArgs> && ...)>* = nullptr> auto operator() (mytuple_t& tup, std::string const& order, T n) const { throw; }Next, we have to provide a version of the functor for each type in AllArgs.... We can achieve this with template specialization. We begin by defining a generic case
template<typename T> struct SetTuple;then the different specializations, one per possible type in the variant:
template<> struct SetTuple<int> { template<typename...Args> auto operator() (std::tuple<Args...>& tup, std::string const& order, int) const { std::get<int>(tup) = std::stoi(order); } }; template<> struct SetTuple<std::string> { template<typename...Args> auto operator() (std::tuple<Args...>& tup, std::string const& order, std::string const&) const { std::get<std::string>(tup) = order; } }; template<> struct SetTuple<double> { template<typename...Args> auto operator() (std::tuple<Args...>& tup, std::string const& order, double const&) const { std::get<double>(tup) = std::stod(order); } };An interesting point here is that the third parameter acts like a tag that allows to distinguish the different overloads; we actually don't need the value of the argument.
We can finish the Greggs definition with some mixins mechanism, by inheriting from SetTuple<T> with T being one of the type in ArgAlls.... The final step is to have access to each inherited operator() within Greggs which can be done with using.
In the end, the whole code would be
#include <array> #include <string> #include <tuple> #include <variant> #include <fmt/ranges.h> template<typename T> struct SetTuple; template<> struct SetTuple<int> { template<typename...Args> auto operator() (std::tuple<Args...>& tup, std::string const& order, int) const { std::get<int>(tup) = std::stoi(order); } }; template<> struct SetTuple<std::string> { template<typename...Args> auto operator() (std::tuple<Args...>& tup, std::string const& order, std::string const&) const { std::get<std::string>(tup) = order; } }; template<> struct SetTuple<double> { template<typename...Args> auto operator() (std::tuple<Args...>& tup, std::string const& order, double const&) const { std::get<double>(tup) = std::stod(order); } }; using myvarian_t = std::variant<int, std::string, double>; template<typename... AllArgs> struct Greggs : SetTuple<AllArgs>... { using SetTuple<AllArgs>::operator()...; using mytuple_t = std::tuple<AllArgs...>; constexpr Greggs(AllArgs... args) : flavours({args...}) {} bool makeOrder(std::string const& order, size_t index) { if (index >= this->flavours.size()) return false; std::visit ([&] (auto const& e) { (*this) (sausage_roll, order, e); }, this->flavours[index]); fmt::println ("sausage_roll: {}", sausage_roll); return true; } template<typename T, std::enable_if_t<(not std::is_same_v<T,AllArgs> && ...)>* = nullptr> auto operator() (mytuple_t& tup, std::string const& order, T n) const { throw; } mytuple_t sausage_roll; std::array<myvarian_t, sizeof...(AllArgs)> flavours; }; int main() { Greggs(0, std::string("")).makeOrder("Vegan", 1); Greggs(0).makeOrder("1", 0); Greggs(std::string("")).makeOrder("73", 0); Greggs(0, std::string(""), 0.0).makeOrder("3.14", 2); return 0; }Note the usage of the fmt library that allows to directly dump (in C++17) the tuple content.
Possible output
sausage_roll: (0, "Vegan") sausage_roll: (1) sausage_roll: ("73") sausage_roll: (0, "", 3.14)