A further digression, because it turns out I want to be able to permute a tuple at run time. That means treating the element of a tuple generically. And I can just barely do this, for some tuples, in c++17. So a slight digression into ADTs. Which in this case means Algebraic Data Types, not Abstract Data Types. But just algebra. No calculus, or differentiation, of data types. Not today.
void is related, but not exactly the same, because it also represents an empty argument list, a function that returns, but does not return any value (a subroutine), and is also functions as the universal pointer.
C++17 recently standardized a sum type to go with the long standardized std::tuple, std::variant. Std::variant remembers which of the alternative types was last set. It is almost never empty, only so if a write into one of the alternatives threw an exception. It is not allowed to hold void, references, arrays, or to contain no types. This is a bit unfortunate, because except for void std::tuple can do all of those things.
There were several competing models for what std::variant should be, with various tradeoffs being made. It was always clear that std::tuple had to be able to represent everything a struct can, and in fact there are now language features to destructure a struct into a tuple. There is no equivalent model for sum types. Union can't hold anything but trivial types because there is no mechanism to track what to do on destruction, since there is no built-in mechanism to determine what the union was last written as.
One of the popular models for variant rises out of database-like interfaces. Even though databases are internally strongly typed, SQL queries are not. And the model of sending text over and getting some kind of response back makes it difficult to expose that to a host language. Particularly when the database schema may change, the query still be perfectly valid, but no longer return the same types. However, since we do know there is a relatively short list of permitted types in the database, a variant that allows just those types and the ability to query what type was returned can be quite useful, and not terribly hard to implement. There are JSON parsers taking similar approaches, only with the addition that a JSON type may have JSON objects contained in them recursively, and those have to be outside the object somehow, or the size of the object is unbounded.
From the implementors point of view, supporting pointers and arrays is a huge amout of extra work. Not allowing an array to decay to a pointer is quite difficult. References have issues when treated generically. Not to mention that references have decidely odd semantics in the differences between construction and assignment. And the degenerate case of an empty variant was also difficult. If that needs to be represented, the type std::monostate has been introduced, which is a type designed to have exactly one item in it, so that all instances of std::monostate are identical. This is also the same as the unit type for product types. It's not an accident that it's represented in Haskell as (), which is the empty tuple. All empty lists are equivalent. It could have been std::tuple<>, but no one in the room happened to think of that.
template <typename... Types auto getElement(size_t i, std::tuple<Types...> tuple) -> std::variant<Types...>;
template <typename Func, typename Tuple, std::size_t... I> void tuple_for_each_impl(Tuple&& tuple, Func&& f, std::index_sequence<I...>) { auto swallow = {0, (std::forward<Func>(f)( I, std::get<I>(std::forward<Tuple>(tuple))))...}; (void)swallow; } template <typename Func, typename... Args> void tuple_for_each(std::tuple<Args...> const& tuple, Func&& f) { tuple_for_each_impl(tuple, f, std::index_sequence_for<Args...>{}); } template <typename... Args> void print(std::ostream& os, std::tuple<Args...> const& tuple) { auto printer = [&os](auto i, auto el) { os << (i == 0 ? "" : ", ") << el; return 0; }; return tuple_for_each(tuple, printer); }
template <typename Func, typename Tuple, std::size_t... I> auto tuple_transform_impl(Tuple&& tuple, Func&& f, std::index_sequence<I...>) { return std::make_tuple( std::forward<Func>(f)(std::get<I>(std::forward<Tuple>(tuple)))...); } template <typename Func, typename... Args> auto tuple_transform(std::tuple<Args...>&& tuple, Func&& f) { return tuple_transform_impl(tuple, f, std::index_sequence_for<Args...>{}); } template <typename Func, typename... Args> auto tuple_transform(std::tuple<Args...> const& tuple, Func&& f) { return tuple_transform_impl(tuple, f, std::index_sequence_for<Args...>{}); }
std::tuple<int, double, long> t = std::make_tuple(1, 2.3, 1l); auto transform = tupleutil::tuple_transform(t, [](auto i) { return i + 1; }); EXPECT_EQ(3.3, std::get<1>(transform)); auto t2 = tupleutil::tuple_transform(std::make_tuple(4, 5.0), [](auto i) { return i + 1; }); EXPECT_EQ(6, std::get<1>(t2));
template <typename... Args, std::size_t... I> constexpr std::array<std::variant<Args...>, sizeof...(Args)> tuple_to_array_impl(std::tuple<Args...> const& tuple, std::index_sequence<I...>) { using V = std::variant<Args...>; std::array<V, sizeof...(Args)> array = { {V(std::in_place_index_t<I>{}, std::get<I>(tuple))...}}; return array; } template <typename... Args> constexpr std::array<std::variant<Args...>, sizeof...(Args)> tuple_to_array(std::tuple<Args...> const& tuple) { return tuple_to_array_impl(tuple, std::index_sequence_for<Args...>{}); }
TEST(TupleTest, to_array) { constexpr std::tuple<int, double, long> t = std::make_tuple(1, 2.3, 1l); auto arr = tupleutil::tuple_to_array(t); int i = std::get<int>(arr[0]); EXPECT_EQ(1, i); } TEST(TupleTest, to_array_repeated) { constexpr std::tuple<int, int, int> t = std::make_tuple(1, 2, 3); auto arr = tupleutil::tuple_to_array(t); int i = std::get<2>(arr[2]); EXPECT_EQ(3, i); }
constexpr std::tuple<int, double, long> t = std::make_tuple(1, 2.3, 1l); std::variant<int, double, long> v0{1}; auto v = tupleutil::get(0, t); EXPECT_EQ(v0, v);
constexpr std::tuple<int, double, long> t = std::make_tuple(1, 2.3, 1l); std::variant<int, double, long> v0{1}; auto v = tupleutil::get(0, t); EXPECT_EQ(v0, v);
template <typename V, typename T, size_t I> auto get_getter() { return [](T const& t) { return V{std::in_place_index_t<I>{}, std::get<I>(t)}; }; } template <typename... Args, std::size_t... I> auto tuple_getters_impl(std::index_sequence<I...>) { using V = std::variant<Args...>; using T = std::tuple<Args...>; using F = V (*)(T const&); std::array<F, sizeof...(Args)> array // = {{[](T const& tuple){return V{std::get<I>(tuple)};}...}}; = {{get_getter<V, T, I>()...}}; return array; } template <typename... Args> auto tuple_getters(std::tuple<Args...>) { return tuple_getters_impl<Args...>(std::index_sequence_for<Args...>{}); }
const auto get = [](size_t i, auto t) { static auto tbl = tupleutil::tuple_getters(t); return tbl[i](t); };
tbl[i](t)
TEST(TupleTest, gettersStatic) { constexpr std::tuple<int, double, long> t = std::make_tuple(1, 2.3, 1l); std::variant<int, double, long> v0{1}; auto v = tupleutil::get(0, t); EXPECT_EQ(v0, v); int i = std::get<0>(v); EXPECT_EQ(1, i); auto v2 = tupleutil::get(1, t); EXPECT_EQ(1ul, v2.index()); double d = std::get<double>(v2); EXPECT_EQ(2.3, d); constexpr auto t2 = std::make_tuple(2.4, 1l); auto v3 = tupleutil::get(0, t2); double d2 = std::get<double>(v3); EXPECT_EQ(2.4, d2); }