From f8a0dbcda8ad463847e72adc1f7473b41205e713 Mon Sep 17 00:00:00 2001 From: Lasse Hinrichsen <lh1887@mi.fu-berlin.de> Date: Sat, 21 Oct 2017 10:00:37 +0200 Subject: [PATCH] Add std::variant fallback This is a fallback implementation of C++17's std::variant. It does not model the complete STL interface but the basis functionality is there. There are probably many corner cases this implementation can not handle yet. This should probably be in dune-common, but for now, I'll put it here. --- dune/subgrid/CMakeLists.txt | 1 + dune/subgrid/common/CMakeLists.txt | 5 + dune/subgrid/common/variant.hh | 387 +++++++++++++++++++++++++++++ dune/subgrid/test/CMakeLists.txt | 2 +- dune/subgrid/test/testvariant.cc | 107 ++++++++ 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 dune/subgrid/common/CMakeLists.txt create mode 100644 dune/subgrid/common/variant.hh create mode 100644 dune/subgrid/test/testvariant.cc diff --git a/dune/subgrid/CMakeLists.txt b/dune/subgrid/CMakeLists.txt index 5f078aa..c09b0c4 100644 --- a/dune/subgrid/CMakeLists.txt +++ b/dune/subgrid/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(common) add_subdirectory(facetools) add_subdirectory(subgrid) add_subdirectory(test) diff --git a/dune/subgrid/common/CMakeLists.txt b/dune/subgrid/common/CMakeLists.txt new file mode 100644 index 0000000..a31ec91 --- /dev/null +++ b/dune/subgrid/common/CMakeLists.txt @@ -0,0 +1,5 @@ +set(commonincludedir ${CMAKE_INSTALL_INCLUDEDIR}/dune/subgrid/common) +set(commoninclude_HEADERS variant.hh) +# include not needed for CMake +# include $(top_srcdir)/am/global-rules +install(FILES ${commoninclude_HEADERS} DESTINATION ${commonincludedir}) diff --git a/dune/subgrid/common/variant.hh b/dune/subgrid/common/variant.hh new file mode 100644 index 0000000..e2132ea --- /dev/null +++ b/dune/subgrid/common/variant.hh @@ -0,0 +1,387 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: +#ifndef DUNE_COMMON_STD_VARIANT_HH +#define DUNE_COMMON_STD_VARIANT_HH +#include <tuple> +#include <memory> +#include <dune/common/hybridutilities.hh> +#include <dune/common/exceptions.hh> + +namespace Dune { +namespace Std { +namespace Impl { + + /* helper constructs to find position of a type T in a pack Ts... */ + template <typename T, typename... Ts> + struct index_in_pack; + + template <typename T, typename... Ts> + struct index_in_pack<T, T, Ts...> : std::integral_constant<std::size_t, 0> {}; + + template <typename T, typename U, typename... Ts> + struct index_in_pack<T, U, Ts...> : std::integral_constant<std::size_t, 1 + index_in_pack<T, Ts...>::value> {}; + + /* end helper constructs to find position of a type T in a pack Ts... */ + + template<typename Tp> + struct Buffer_ : std::aligned_storage<sizeof(Tp)> { + using Storage = typename std::aligned_storage_t<sizeof(Tp)>::type; + Storage storage_; + + void* addr() { + return static_cast<void*>(&storage_); + } + + const void* addr() const { + return static_cast<const void*>(&storage_); + } + + Tp* ptr() { + return static_cast<Tp*>(addr()); + } + + const Tp* ptr() const { + return static_cast<const Tp*>(addr()); + } + }; + + template<typename Tp, bool isTrivial> + struct TypeStorage_ { }; + + template<typename Tp> + struct TypeStorage_<Tp, true> { + TypeStorage_(Tp t) : + tp_(t) {} + + template<typename... Args> + TypeStorage_(Args... args) : + tp_(args...) {} + + auto& get() { + return tp_; + } + const auto& get() const { + return tp_; + } + private: + Tp tp_; + }; + + template<typename Tp> + struct TypeStorage_<Tp, false> { + TypeStorage_(Tp t) { + ::new (&tp_) Tp(t); + } + + template<typename... Args> + TypeStorage_(Args... args) { + ::new (&tp_) Tp(std::forward<Args>(args)...); + } + + auto& get() { + return *(tp_.ptr()); + } + const auto& get() const { + return *(tp_.ptr()); + } + + private: + Buffer_<Tp> tp_; + }; + + template<typename... T> + union variant_union_ {}; + + template<typename Head_, typename... Tail_> + union variant_union_<Head_, Tail_...> { + constexpr variant_union_() : + tail_() {} + + template<typename... Args> + constexpr variant_union_(std::integral_constant<size_t, 0>, Args&&... args) : + head_(std::forward<Args...>(args)...) {} + + template<size_t N, typename... Args> + constexpr variant_union_(std::integral_constant<size_t, N>, Args&&... args) : + tail_(std::integral_constant<size_t, N-1>(), std::forward<Args...>(args)...) {} + + auto& getByIndex(std::integral_constant<size_t, 0>) { + return head_.get(); + } + + const auto& getByIndex(std::integral_constant<size_t, 0>) const { + return head_.get(); + } + + + template<size_t N> + auto& getByIndex(std::integral_constant<size_t, N>) { + return tail_.getByIndex(std::integral_constant<size_t, N-1>()); + } + + template<size_t N> + const auto& getByIndex(std::integral_constant<size_t, N>) const { + return tail_.getByIndex(std::integral_constant<size_t, N-1>()); + } + + template<typename Tp> + void set(Tp obj) { + Dune::Hybrid::ifElse(std::is_same<Tp, Head_>(), + [&](auto&& id) { head_=std::move(id(obj)); }, + [&](auto&& id) { return id(tail_).set(std::move(obj)); } + ); + } + + constexpr size_t size() const { + return sizeof...(Tail_)+1; + } + + private: + TypeStorage_<Head_, std::is_literal_type<Head_>::value> head_; + variant_union_<Tail_...> tail_; + }; + + template<typename...T> + struct variant_{ + + constexpr variant_() : + unions_() {} + + template<typename Tp> + constexpr variant_(Tp obj) : + unions_(), + index_(index_in_pack<Tp, T...>::value) + { + unions_.set(std::move(obj)); + } + + template<typename Tp> + auto& get() { + constexpr size_t idx = index_in_pack<Tp, T...>::value; + if (index_ != idx) + DUNE_THROW(Dune::Exception, "Bad variant access."); + + return get<idx>(); + } + + template<typename Tp> + const auto& get() const { + constexpr size_t idx = index_in_pack<Tp, T...>::value; + if (index_ != idx) + DUNE_THROW(Dune::Exception, "Bad variant access."); + + return get<idx>(); + } + + template<typename Tp> + Tp* get_if() { + if (not holds_alternative<Tp>()) + return (Tp*) nullptr; + else + return &(get<Tp>()); + } + + template<typename Tp> + const Tp* get_if() const { + if (not holds_alternative<Tp>()) + return (Tp*) nullptr; + else + return &(get<Tp>()); + } + + template<size_t N> + auto* get_if() { + using Tp = std::decay_t<decltype(get<N>())>; + if (not holds_alternative<N>()) + return (Tp*) nullptr; + else + return &(get<Tp>()); + } + + template<size_t N> + const auto* get_if() const { + using Tp = std::decay_t<decltype(get<N>())>; + if (not holds_alternative<N>()) + return (Tp*) nullptr; + else + return &(get<Tp>()); + } + + template<size_t N> + auto& get() { + if (index_ != N) + DUNE_THROW(Dune::Exception, "Bad variant access."); + return unions_.template getByIndex(std::integral_constant<size_t, N>()); + } + template<size_t N> + const auto& get() const { + if (index_ != N) + DUNE_THROW(Dune::Exception, "Bad variant access."); + return unions_.template getByIndex(std::integral_constant<size_t, N>()); + } + + template<typename Tp> + constexpr Tp& operator=(Tp obj) { + unions_.set(std::move(obj)); + constexpr auto index = index_in_pack<Tp, T...>::value; + index_=index; + return unions_.getByIndex(std::integral_constant<size_t,index>()); + } + + constexpr std::size_t index() const noexcept { + return index_; + } + + constexpr auto size() const { + return sizeof...(T); + } + + /* \brief Apply visitor to the active variant. + * + * visit assumes that the result of + * func(T) has the same type for all types T + * in this variant. + */ + template<typename F> + auto visit(F&& func) { + using namespace Dune::Hybrid; + + using Result = decltype(func(unions_.getByIndex(std::integral_constant<size_t, 0>()))); + + return ifElse(std::is_same<Result, void>(), [&, this](auto id) { + constexpr auto tsize = size_; + Dune::Hybrid::forEach(Dune::Hybrid::integralRange(std::integral_constant<size_t, tsize>()), [&](auto i) { + if (i==this->index_) + func(id(unions_).getByIndex(std::integral_constant<size_t, i>())); + }); + return;}, + [&func,this](auto id) { + constexpr auto tsize = size_; + + auto result = std::unique_ptr<Result>(); + + Dune::Hybrid::forEach(Dune::Hybrid::integralRange(std::integral_constant<size_t, tsize>()), [&, this](auto i) { + if (i==this->index_) + result = std::make_unique<Result>(func(id(this->unions_).getByIndex(std::integral_constant<size_t, i>()))); + }); + return *result; + }); + } + + template<typename F> + auto visit(F&& func) const { + using namespace Dune::Hybrid; + + using Result = decltype(func(unions_.getByIndex(std::integral_constant<size_t, 0>()))); + + return ifElse(std::is_same<Result, void>(), [&, this](auto id) { + constexpr auto tsize = size_; + Dune::Hybrid::forEach(Dune::Hybrid::integralRange(std::integral_constant<size_t, tsize>()), [&](auto i) { + if (i==this->index_) + func(id(unions_).getByIndex(std::integral_constant<size_t, i>())); + }); + return;}, + [&func,this](auto id) { + constexpr auto tsize = size_; + + auto result = std::unique_ptr<Result>(); + + Dune::Hybrid::forEach(Dune::Hybrid::integralRange(std::integral_constant<size_t, tsize>()), [&, this](auto i) { + if (i==this->index_) + result = std::make_unique<Result>(func(id(this->unions_).getByIndex(std::integral_constant<size_t, i>()))); + }); + return *result; + }); + } + + /** \brief Check if a given type is the one that is currently active in the variant. */ + template<typename Tp> + constexpr bool holds_alternative() const { + // I have no idea how this could be really constexpr, but for STL-conformity, + // I'll leave the modifier there. + return (index_in_pack<Tp, T...>::value == index_); + } + + /** \brief Check if a given type is the one that is currently active in the variant. */ + template<size_t N> + constexpr bool holds_alternative() const { + // I have no idea how this could be really constexpr, but for STL-conformity, + // I'll leave the modifier there. + return (N == index_); + } + + private: + variant_union_<T...> unions_; + std::size_t index_; + constexpr static auto size_ = sizeof...(T); + }; + +} // end namespace Impl + + /** \brief Incomplete re-implementation of C++17's std::variant. */ + template<typename ...T> + using variant = Impl::variant_<T...>; + + template<size_t N, typename... T> + auto& get(variant<T...>& var) { + return var.template get<N>(); + } + + template<size_t N, typename... T> + const auto& get(const variant<T...>& var) { + return var.template get<N>(); + } + + template<typename F, typename... T> + auto visit(F&& visitor, variant<T...>& var) { + return var.visit(std::forward<F>(visitor)); + } + + template<typename F, typename... T> + auto visit(F&& visitor, const variant<T...>& var) { + return var.visit(std::forward<F>(visitor)); + } + + template<typename Tp, typename ...T> + auto& get(variant<T...>& var) { + return var.template get<Tp>(); + } + + template<typename Tp, typename ...T> + const auto& get(const variant<T...>& var) { + return var.template get<Tp>(); + } + + template<typename Tp, typename ...T> + const auto* get_if(const variant<T...>& var) { + return var.template get_if<Tp>(); + } + + template<typename Tp, typename ...T> + auto* get_if(variant<T...>& var) { + return var.template get_if<Tp>(); + } + + template<size_t N, typename ...T> + const auto* get_if(const variant<T...>& var) { + return var.template get_if<N>(); + } + + template<size_t N, typename ...T> + auto* get_if(variant<T...>& var) { + return var.template get_if<N>(); + } + + template<typename Tp, typename ...T> + constexpr bool holds_alternative(const variant<T...>& var) { + return var.template holds_alternative<Tp>(); + } + + template <typename... T> + constexpr auto variant_size_v(const variant<T...>&) { + return std::integral_constant<std::size_t,sizeof...(T)>::value; + } + +} // end namespace Std +} // end namespace Dune +#endif diff --git a/dune/subgrid/test/CMakeLists.txt b/dune/subgrid/test/CMakeLists.txt index 6bc998a..81d3862 100644 --- a/dune/subgrid/test/CMakeLists.txt +++ b/dune/subgrid/test/CMakeLists.txt @@ -1,5 +1,5 @@ -set(TESTPROGS test-w-onedgrid test-w-yaspgrid test-w-alugrid) +set(TESTPROGS test-w-onedgrid test-w-yaspgrid test-w-alugrid testvariant) if(ALBERTA_FOUND) list(APPEND TESTPROGS test-w-albertagrid2d) diff --git a/dune/subgrid/test/testvariant.cc b/dune/subgrid/test/testvariant.cc new file mode 100644 index 0000000..ca0393f --- /dev/null +++ b/dune/subgrid/test/testvariant.cc @@ -0,0 +1,107 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <iostream> +#include <dune/common/parallel/mpihelper.hh> // An initializer of MPI +#include <dune/common/exceptions.hh> // We use exceptions +#include <dune/common/test/testsuite.hh> + +#include <dune/subgrid/common/variant.hh> + +// some non-default constructible type +struct F { + int i; + F() = delete; + F(int j) : + i(j) {} +}; + +Dune::TestSuite testVariant() { + using namespace Dune; + TestSuite suite; + + int i = 42; + double d = 3.14; + F f(13); + using V = std::vector<int>; + + auto variant = Std::variant<int, double, F, V>(); + + suite.check(Std::variant_size_v(variant) == 4, "Test variant_size_v"); + + + variant = d; + suite.check(Std::holds_alternative<double>(variant), "Test holds_alternative"); + + variant = f; + suite.check(Std::holds_alternative<F>(variant), "Test holds_alternative"); + + variant = i; + suite.check(Std::holds_alternative<int>(variant), "Test holds_alternative"); + + suite.check(Std::get<int>(variant) == i, "Test get<Type>"); + suite.check(Std::get<0>(variant) == i, "Test get<Index>"); + + suite.check(Std::get_if<int>(variant) != nullptr, "Test get_if on right type"); + suite.check(Std::get_if<double>(variant) == nullptr, "Test get_if on wrong type"); + + suite.check(Std::get_if<0>(variant) != nullptr, "Test get_if on right index"); + suite.check(Std::get_if<1>(variant) == nullptr, "Test get_if on wrong index"); + + // test if get<Type> throws if one tries to get the wrong type: + try { + // currently hold type is still int, so double should throw + Std::get<double>(variant); + suite.check(false, "Test get<Type> on wrong type should have thrown"); + } + catch (...) { + suite.check(true, "Test get<Type> on wrong type has thrown"); + } + + + variant = V(1); + suite.check(Std::get<V>(variant).size() == 1, "Test with non-trivial type"); + + variant = f; + + suite.check(variant.index() == 2, "Test index()"); // we're at type F, which has position 2 + + // Demonstrate visit concept and using vector as an example of a non-POD type + using V2 = std::vector<double>; + Std::variant<V, V2> variant2; + variant2 = V(1); + auto size = [](auto&& v) {return v.size();}; + suite.check(Std::visit(size, variant2)== 1, "Test visit"); + variant2 = V2(2); + suite.check(Std::visit(size, variant2)== 2, "Test visit"); + + // try on a const reference: + const auto& constv2 = variant2; + suite.check(Std::visit(size, constv2)== 2, "Test const visit"); + suite.check(Std::get_if<V2>(constv2) != nullptr, "Test const get_if"); + + + return suite; +} + +int main(int argc, char** argv) +{ + try{ + // Maybe initialize MPI + Dune::MPIHelper::instance(argc, argv); + + Dune::TestSuite suite; + suite.subTest(testVariant()); + + return suite.exit(); + } + catch (Dune::Exception &e){ + std::cerr << "Dune reported error: " << e << std::endl; + } + catch (...){ + std::cerr << "Unknown exception thrown!" << std::endl; + } +} -- GitLab