Commit 6dce5bfe authored by Praetorius, Simon's avatar Praetorius, Simon

Merge branch 'feature/expansion_statements' into 'develop'

Reduce errors and compiletime in tree traversal

See merge request spraetor/dune-amdis!93
parents efebae43 0689eaae
Pipeline #1815 passed with stage
in 25 minutes and 12 seconds
#include(CheckIncludeFileCXX)
include(CheckCXXSourceCompiles) include(CheckCXXSourceCompiles)
#include(CheckCXXSymbolExists)
# fold expressions (a + ...) # fold expressions (a + ...)
check_cxx_source_compiles(" check_cxx_source_compiles("
...@@ -30,4 +28,16 @@ check_cxx_source_compiles(" ...@@ -30,4 +28,16 @@ check_cxx_source_compiles("
return f<1>(); return f<1>();
} }
" AMDIS_HAS_CXX_CONSTEXPR_IF " AMDIS_HAS_CXX_CONSTEXPR_IF
)
check_cxx_source_compiles("
#include <iostream>
#include <tuple>
int main()
{
auto tup = std::make_tuple(0, 'a', 3.14);
for... (auto elem : tup)
std::cout << elem << std::endl;
}
" AMDIS_HAS_EXPANSION_STATEMENTS
) )
\ No newline at end of file
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
/* some detected compiler features may be used in AMDiS */ /* some detected compiler features may be used in AMDiS */
#cmakedefine AMDIS_HAS_CXX_FOLD_EXPRESSIONS 1 #cmakedefine AMDIS_HAS_CXX_FOLD_EXPRESSIONS 1
#cmakedefine AMDIS_HAS_CXX_CONSTEXPR_IF 1 #cmakedefine AMDIS_HAS_CXX_CONSTEXPR_IF 1
#cmakedefine AMDIS_HAS_EXPANSION_STATEMENTS 1
/* end amdis /* end amdis
Everything below here will be overwritten Everything below here will be overwritten
......
...@@ -24,4 +24,4 @@ add_dependencies(examples ...@@ -24,4 +24,4 @@ add_dependencies(examples
stokes1.2d stokes1.2d
stokes3.2d stokes3.2d
navier_stokes.2d navier_stokes.2d
convection_diffusion.2d) convection_diffusion.2d)
\ No newline at end of file
...@@ -146,9 +146,10 @@ namespace AMDiS ...@@ -146,9 +146,10 @@ namespace AMDiS
if (!segment.boundary()) if (!segment.boundary())
continue; continue;
auto index = segment.boundarySegmentIndex(); Dune::Hybrid::ifElse(Dune::Std::is_detected<HasBoundaryId, Segment>{}, [&](auto id) {
Dune::Hybrid::ifElse(Dune::Std::is_detected<HasBoundaryId, Segment>{}, auto index = segment.boundarySegmentIndex();
[&](auto id) { boundaryIds_[index] = id(segment).boundaryId(); }); boundaryIds_[index] = id(segment).boundaryId();
});
} }
} }
} }
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
#include <amdis/Output.hpp> #include <amdis/Output.hpp>
#include <amdis/common/ConcurrentCache.hpp> #include <amdis/common/ConcurrentCache.hpp>
#include <amdis/typetree/Traversal.hpp>
#include <amdis/typetree/TreeContainer.hpp> #include <amdis/typetree/TreeContainer.hpp>
#include <amdis/typetree/Visitor.hpp>
namespace AMDiS namespace AMDiS
{ {
...@@ -135,7 +135,7 @@ namespace AMDiS ...@@ -135,7 +135,7 @@ namespace AMDiS
auto lv = basis_->localView(); auto lv = basis_->localView();
auto const& idSet = gv.grid().localIdSet(); auto const& idSet = gv.grid().localIdSet();
forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { for_each_leaf_node(lv.tree(), [&](auto const& node, auto const& tp) {
nodeDataTransfer_[tp].preAdaptInit(lv, coeff, node); nodeDataTransfer_[tp].preAdaptInit(lv, coeff, node);
}); });
...@@ -148,7 +148,7 @@ namespace AMDiS ...@@ -148,7 +148,7 @@ namespace AMDiS
lv.bind(e); lv.bind(e);
auto& treeContainer = it.first->second; auto& treeContainer = it.first->second;
forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { for_each_leaf_node(lv.tree(), [&](auto const& node, auto const& tp) {
nodeDataTransfer_[tp].cacheLocal(treeContainer[tp]); nodeDataTransfer_[tp].cacheLocal(treeContainer[tp]);
}); });
} }
...@@ -201,7 +201,7 @@ namespace AMDiS ...@@ -201,7 +201,7 @@ namespace AMDiS
}; };
restrictLocalCompleted = true; restrictLocalCompleted = true;
forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { for_each_leaf_node(lv.tree(), [&](auto const& node, auto const& tp) {
restrictLocalCompleted &= restrictLocalCompleted &=
nodeDataTransfer_[tp].restrictLocal(father, treeContainer[tp], xInChildCached, nodeDataTransfer_[tp].restrictLocal(father, treeContainer[tp], xInChildCached,
childContainer[tp], init); childContainer[tp], init);
...@@ -224,7 +224,7 @@ namespace AMDiS ...@@ -224,7 +224,7 @@ namespace AMDiS
auto gv = basis_->gridView(); auto gv = basis_->gridView();
auto lv = basis_->localView(); auto lv = basis_->localView();
auto const& idSet = gv.grid().localIdSet(); auto const& idSet = gv.grid().localIdSet();
forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { for_each_leaf_node(lv.tree(), [&](auto const& node, auto const& tp) {
nodeDataTransfer_[tp].postAdaptInit(lv, coeff, node); nodeDataTransfer_[tp].postAdaptInit(lv, coeff, node);
}); });
...@@ -243,7 +243,7 @@ namespace AMDiS ...@@ -243,7 +243,7 @@ namespace AMDiS
if (it != persistentContainer_.end()) { if (it != persistentContainer_.end()) {
lv.bind(e); lv.bind(e);
auto const& treeContainer = it->second; auto const& treeContainer = it->second;
forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { for_each_leaf_node(lv.tree(), [&](auto const& node, auto const& tp) {
nodeDataTransfer_[tp].copyLocal(treeContainer[tp]); nodeDataTransfer_[tp].copyLocal(treeContainer[tp]);
}); });
finished_[index] = true; finished_[index] = true;
...@@ -275,7 +275,7 @@ namespace AMDiS ...@@ -275,7 +275,7 @@ namespace AMDiS
return fatherGeo.local(childGeo.global(x)); return fatherGeo.local(childGeo.global(x));
}; };
forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { for_each_leaf_node(lv.tree(), [&](auto const& node, auto const& tp) {
nodeDataTransfer_[tp].prolongLocal(father, treeContainer[tp], xInFather, init); nodeDataTransfer_[tp].prolongLocal(father, treeContainer[tp], xInFather, init);
}); });
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
#include <amdis/BoundaryCondition.hpp> #include <amdis/BoundaryCondition.hpp>
#include <amdis/common/Concepts.hpp> #include <amdis/common/Concepts.hpp>
#include <amdis/typetree/RangeType.hpp> #include <amdis/typetree/RangeType.hpp>
#include <amdis/typetree/Traversal.hpp>
#include <amdis/typetree/TreeData.hpp> #include <amdis/typetree/TreeData.hpp>
#include <amdis/typetree/Visitor.hpp>
namespace AMDiS namespace AMDiS
{ {
......
...@@ -196,7 +196,7 @@ std::vector<D> PeriodicBC<D,MI>:: ...@@ -196,7 +196,7 @@ std::vector<D> PeriodicBC<D,MI>::
coords(Node const& tree, std::vector<std::size_t> const& localIndices) const coords(Node const& tree, std::vector<std::size_t> const& localIndices) const
{ {
std::vector<D> dofCoords(localIndices.size()); std::vector<D> dofCoords(localIndices.size());
AMDiS::forEachLeafNode_(tree, [&](auto const& node, auto const& tp) for_each_leaf_node(tree, [&](auto const& node, auto const& tp)
{ {
std::size_t size = node.finiteElement().size(); std::size_t size = node.finiteElement().size();
auto geometry = node.element().geometry(); auto geometry = node.element().geometry();
......
...@@ -177,7 +177,7 @@ void ProblemStat<Traits>::createMatricesAndVectors() ...@@ -177,7 +177,7 @@ void ProblemStat<Traits>::createMatricesAndVectors()
rhs_ = std::make_shared<SystemVector>(*globalBasis_, NO_OPERATION); rhs_ = std::make_shared<SystemVector>(*globalBasis_, NO_OPERATION);
auto localView = globalBasis_->localView(); auto localView = globalBasis_->localView();
AMDiS::forEachNode_(localView.tree(), [&,this](auto const& node, auto treePath) for_each_node(localView.tree(), [&,this](auto const& node, auto treePath)
{ {
std::string i = to_string(treePath); std::string i = to_string(treePath);
estimates_[i].resize(globalBasis_->gridView().indexSet().size(0)); estimates_[i].resize(globalBasis_->gridView().indexSet().size(0));
...@@ -205,7 +205,7 @@ void ProblemStat<Traits>::createMarker() ...@@ -205,7 +205,7 @@ void ProblemStat<Traits>::createMarker()
{ {
marker_.clear(); marker_.clear();
auto localView = globalBasis_->localView(); auto localView = globalBasis_->localView();
AMDiS::forEachNode_(localView.tree(), [&,this](auto const& node, auto treePath) for_each_node(localView.tree(), [&,this](auto const& node, auto treePath)
{ {
std::string componentName = name_ + "->marker[" + to_string(treePath) + "]"; std::string componentName = name_ + "->marker[" + to_string(treePath) + "]";
...@@ -232,7 +232,7 @@ void ProblemStat<Traits>::createFileWriter() ...@@ -232,7 +232,7 @@ void ProblemStat<Traits>::createFileWriter()
{ {
filewriter_.clear(); filewriter_.clear();
auto localView = globalBasis_->localView(); auto localView = globalBasis_->localView();
forEachNode_(localView.tree(), [&,this](auto const& node, auto treePath) for_each_node(localView.tree(), [&,this](auto const& node, auto treePath)
{ {
std::string componentName = name_ + "->output[" + to_string(treePath) + "]"; std::string componentName = name_ + "->output[" + to_string(treePath) + "]";
...@@ -428,9 +428,9 @@ buildAfterAdapt(AdaptInfo& /*adaptInfo*/, Flag /*flag*/, bool asmMatrix, bool as ...@@ -428,9 +428,9 @@ buildAfterAdapt(AdaptInfo& /*adaptInfo*/, Flag /*flag*/, bool asmMatrix, bool as
rhs_->init(asmVector); rhs_->init(asmVector);
auto localView = globalBasis_->localView(); auto localView = globalBasis_->localView();
forEachNode_(localView.tree(), [&,this](auto const& rowNode, auto rowTp) { for_each_node(localView.tree(), [&,this](auto const& rowNode, auto rowTp) {
auto rowBasis = Dune::Functions::subspaceBasis(*globalBasis_, rowTp); auto rowBasis = Dune::Functions::subspaceBasis(*globalBasis_, rowTp);
forEachNode_(localView.tree(), [&,this](auto const& colNode, auto colTp) { for_each_node(localView.tree(), [&,this](auto const& colNode, auto colTp) {
auto colBasis = Dune::Functions::subspaceBasis(*globalBasis_, colTp); auto colBasis = Dune::Functions::subspaceBasis(*globalBasis_, colTp);
for (auto bc : dirichletBCs_[rowNode][colNode]) for (auto bc : dirichletBCs_[rowNode][colNode])
bc->init(rowBasis, colBasis); bc->init(rowBasis, colBasis);
...@@ -456,8 +456,8 @@ buildAfterAdapt(AdaptInfo& /*adaptInfo*/, Flag /*flag*/, bool asmMatrix, bool as ...@@ -456,8 +456,8 @@ buildAfterAdapt(AdaptInfo& /*adaptInfo*/, Flag /*flag*/, bool asmMatrix, bool as
systemMatrix_->finish(asmMatrix); systemMatrix_->finish(asmMatrix);
rhs_->finish(asmVector); rhs_->finish(asmVector);
forEachNode_(localView.tree(), [&,this](auto const& rowNode, auto) { for_each_node(localView.tree(), [&,this](auto const& rowNode, auto) {
forEachNode_(localView.tree(), [&,this](auto const& colNode, auto) { for_each_node(localView.tree(), [&,this](auto const& colNode, auto) {
// finish boundary condition // finish boundary condition
for (auto bc : dirichletBCs_[rowNode][colNode]) for (auto bc : dirichletBCs_[rowNode][colNode])
bc->fillBoundaryCondition(*systemMatrix_, *solution_, *rhs_, rowNode, colNode); bc->fillBoundaryCondition(*systemMatrix_, *solution_, *rhs_, rowNode, colNode);
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
#include <initializer_list> #include <initializer_list>
#include <amdis/common/Apply.hpp>
#include <amdis/common/Index.hpp> #include <amdis/common/Index.hpp>
#include <amdis/common/Range.hpp> #include <amdis/common/Range.hpp>
...@@ -16,34 +15,52 @@ namespace AMDiS ...@@ -16,34 +15,52 @@ namespace AMDiS
void ignored_evaluation(std::initializer_list<T>&&) { /* do nothing */ } void ignored_evaluation(std::initializer_list<T>&&) { /* do nothing */ }
} }
template <std::size_t... I, class Tuple, class Functor>
constexpr void for_each(std::index_sequence<I...>, Tuple&& tuple, Functor&& f)
{
using std::get;
#if AMDIS_HAS_EXPANSION_STATEMENTS
for... (auto&& t : tuple) { f(FWD(t)); }
#elif AMDIS_HAS_CXX_FOLD_EXPRESSIONS
(f(get<I>(tuple)),...);
#else
Impl_::ignored_evaluation<int>({0, (f(get<I>(tuple)), 0)...});
#endif
}
template <class Tuple, class Functor> template <class Tuple, class Functor>
constexpr void for_each(Tuple&& tuple, Functor&& f) constexpr void for_each(Tuple&& tuple, Functor&& f)
{ {
#if AMDIS_HAS_CXX_FOLD_EXPRESSIONS Tools::for_each(std::make_index_sequence<Size_v<std::remove_reference_t<Tuple>>>{}, FWD(tuple), FWD(f));
Tools::apply([f=std::move(f)](auto&&... t) { (f(FWD(t)),...); }, tuple); }
#else
Tools::apply([f=std::move(f)](auto&&... t) {
Impl_::ignored_evaluation<int>({0, (f(FWD(t)), 0)...}); template <std::size_t I0 = 0, std::size_t... I, class Functor>
}, tuple); constexpr void for_range(std::index_sequence<I...>, Functor&& f)
#endif {
#if AMDIS_HAS_CXX_FOLD_EXPRESSIONS
(f(index_t<I0+I>{}),...);
#else
Impl_::ignored_evaluation<int>({0, (f(index_t<I0+I>{}), 0)...});
#endif
} }
template <std::size_t I0, std::size_t I1, class Functor> template <std::size_t I0, std::size_t I1, class Functor>
constexpr void for_range(index_t<I0> i0, index_t<I1> i1, Functor&& f) constexpr void for_range(index_t<I0> i0, index_t<I1> i1, Functor&& f)
{ {
Tools::for_each(range_t<I0,I1>{}, FWD(f)); Tools::for_range<I0>(std::make_index_sequence<std::size_t(I1-I0)>{}, FWD(f));
} }
template <std::size_t N, class Functor> template <std::size_t N, class Functor>
constexpr void for_range(index_t<N>, Functor&& f) constexpr void for_range(index_t<N>, Functor&& f)
{ {
Tools::for_each(range_t<0,N>{}, FWD(f)); Tools::for_range(std::make_index_sequence<N>{}, FWD(f));
} }
template <std::size_t I0, std::size_t I1, class Functor> template <std::size_t I0, std::size_t I1, class Functor>
constexpr void for_range(Functor&& f) constexpr void for_range(Functor&& f)
{ {
Tools::for_each(range_t<I0,I1>{}, FWD(f)); Tools::for_range<I0>(std::make_index_sequence<std::size_t(I1-I0)>{}, FWD(f));
} }
} // end namespace Tools } // end namespace Tools
......
...@@ -65,6 +65,12 @@ namespace AMDiS ...@@ -65,6 +65,12 @@ namespace AMDiS
template <class T> template <class T>
using owner = T; using owner = T;
/// A functor with no operation
struct NoOp
{
template <class... T>
constexpr void operator()(T&&...) const { /* no nothing */ }
};
/// Create a unique_ptr by copy/move construction /// Create a unique_ptr by copy/move construction
template <class Obj> template <class Obj>
......
...@@ -147,7 +147,7 @@ LocalFunction::operator()(Domain const& x) const ...@@ -147,7 +147,7 @@ LocalFunction::operator()(Domain const& x) const
auto&& coefficients = *globalFunction_.dofVector_; auto&& coefficients = *globalFunction_.dofVector_;
auto&& nodeToRangeEntry = globalFunction_.nodeToRangeEntry_; auto&& nodeToRangeEntry = globalFunction_.nodeToRangeEntry_;
forEachLeafNode_(*subTree_, [&,this](auto const& node, auto const& tp) for_each_leaf_node(*subTree_, [&,this](auto const& node, auto const& tp)
{ {
auto&& fe = node.finiteElement(); auto&& fe = node.finiteElement();
auto&& localBasis = fe.localBasis(); auto&& localBasis = fe.localBasis();
...@@ -193,7 +193,7 @@ GradientLocalFunction::operator()(Domain const& x) const ...@@ -193,7 +193,7 @@ GradientLocalFunction::operator()(Domain const& x) const
auto&& coefficients = *globalFunction_.dofVector_; auto&& coefficients = *globalFunction_.dofVector_;
auto&& nodeToRangeEntry = globalFunction_.nodeToRangeEntry_; auto&& nodeToRangeEntry = globalFunction_.nodeToRangeEntry_;
forEachLeafNode_(*subTree_, [&,this](auto const& node, auto const& tp) for_each_leaf_node(*subTree_, [&,this](auto const& node, auto const& tp)
{ {
// TODO: may DOFVectorView::Range to FieldVector type if necessary // TODO: may DOFVectorView::Range to FieldVector type if necessary
using LocalDerivativeTraits using LocalDerivativeTraits
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#include <amdis/Assembler.hpp> #include <amdis/Assembler.hpp>
#include <amdis/LocalOperator.hpp> #include <amdis/LocalOperator.hpp>
#include <amdis/typetree/Visitor.hpp> #include <amdis/typetree/Traversal.hpp>
#include <amdis/utility/AssembleOperators.hpp> #include <amdis/utility/AssembleOperators.hpp>
namespace AMDiS { namespace AMDiS {
...@@ -75,8 +75,8 @@ assemble(RowLocalView const& rowLocalView, ColLocalView const& colLocalView) ...@@ -75,8 +75,8 @@ assemble(RowLocalView const& rowLocalView, ColLocalView const& colLocalView)
auto const& element = rowLocalView.element(); auto const& element = rowLocalView.element();
auto geometry = element.geometry(); auto geometry = element.geometry();
forEachNode_(rowLocalView.tree(), [&](auto const& rowNode, auto) { for_each_node(rowLocalView.tree(), [&](auto const& rowNode, auto) {
forEachNode_(colLocalView.tree(), [&](auto const& colNode, auto) { for_each_node(colLocalView.tree(), [&](auto const& colNode, auto) {
auto& matOp = operators_[rowNode][colNode]; auto& matOp = operators_[rowNode][colNode];
if (matOp) { if (matOp) {
matOp.bind(element, geometry); matOp.bind(element, geometry);
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#include <amdis/Assembler.hpp> #include <amdis/Assembler.hpp>
#include <amdis/LocalOperator.hpp> #include <amdis/LocalOperator.hpp>
#include <amdis/typetree/Visitor.hpp> #include <amdis/typetree/Traversal.hpp>
#include <amdis/utility/AssembleOperators.hpp> #include <amdis/utility/AssembleOperators.hpp>
namespace AMDiS { namespace AMDiS {
...@@ -66,7 +66,7 @@ assemble(LocalView const& localView) ...@@ -66,7 +66,7 @@ assemble(LocalView const& localView)
auto const& element = localView.element(); auto const& element = localView.element();
auto geometry = element.geometry(); auto geometry = element.geometry();
forEachNode_(localView.tree(), [&](auto const& node, auto) { for_each_node(localView.tree(), [&](auto const& node, auto) {
auto& rhsOp = operators_[node]; auto& rhsOp = operators_[node];
if (rhsOp) { if (rhsOp) {
rhsOp.bind(element, geometry); rhsOp.bind(element, geometry);
......
...@@ -7,5 +7,4 @@ install(FILES ...@@ -7,5 +7,4 @@ install(FILES
TreeContainer.hpp TreeContainer.hpp
TreeData.hpp TreeData.hpp
TreePath.hpp TreePath.hpp
Visitor.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/amdis/typetree) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/amdis/typetree)
...@@ -3,139 +3,257 @@ ...@@ -3,139 +3,257 @@
#include <dune/common/hybridutilities.hh> #include <dune/common/hybridutilities.hh>
#include <dune/common/rangeutilities.hh> #include <dune/common/rangeutilities.hh>
#include <dune/typetree/childextraction.hh>
#include <dune/typetree/nodetags.hh> #include <dune/typetree/nodetags.hh>
#include <dune/typetree/treepath.hh> #include <dune/typetree/treepath.hh>
#include <dune/typetree/visitor.hh> #include <dune/typetree/visitor.hh>
#include <amdis/common/ForEach.hpp> #include <amdis/common/ForEach.hpp>
#include <amdis/common/Logical.hpp>
#include <amdis/common/Range.hpp>
#include <amdis/common/TypeTraits.hpp> #include <amdis/common/TypeTraits.hpp>
namespace AMDiS // NOTE: backport of dune/typetree/traversal.hpp from Dune 2.7
{
// forward declaration of main engine struct namespace AMDiS {
template <typename NodeTag, bool visit = true>
struct TraverseTree; enum class TreePathType
// Do not visit nodes the visitor is not interested in
template <typename NodeTag>
struct TraverseTree<NodeTag, false>
{
template <typename Node, typename Visitor, typename TreePath>
static void apply(const Node& node, const Visitor& visitor, TreePath const& tp)
{}
};
#ifndef DOXYGEN
// some implementation details
template <class Node, class Index>
struct HybridChildType
: HybridChildType<std::remove_const_t<Node>, std::remove_const_t<Index>> {};
template <class Node>
struct HybridChildType<Node, std::size_t>
{
using type = typename Node::template Child<0>::Type;
};
template <class Node, std::size_t K>
struct HybridChildType<Node, Dune::index_constant<K>>
{
using type = typename Node::template Child<K>::Type;
};
template <class NodeTag, class Node>
constexpr std::size_t hybridDegree(NodeTag, Node const& node)
{
return Dune::TypeTree::degree(node);
}
template <class Node>
constexpr auto hybridDegree(Dune::TypeTree::CompositeNodeTag, Node const& node)
{
return Dune::index_constant<Node::CHILDREN>{};
}
template <std::size_t k, std::size_t n>
constexpr bool notLastChild(Dune::index_constant<k> const&, Dune::index_constant<n> const&)
{
return k < n-1;
}
constexpr bool notLastChild(std::size_t k, std::size_t n)
{
return k < n-1;
}
#endif
template <class NodeTag>
struct TraverseTree<NodeTag, true>
{
template <typename N, typename V, typename TreePath>
static void apply(N&& n, V&& v, TreePath const& tp)
{ {
using Node = std::remove_reference_t<N>; DYNAMIC, STATIC
using Visitor = std::remove_reference_t<V>; };
namespace Impl {
// This is a constexpr version of the ternery operator c?t1:t1.
// In contrast to the latter the type of t1 and t2 can be different.
// Notice that std::conditional would not do the trick, because
// it only selects between types.
template<bool c, class T1, class T2,
std::enable_if_t<c, int> = 0>
constexpr auto conditionalValue(T1&& t1, T2&& t2) {
return std::forward<T1>(t1);
}
template<bool c, class T1, class T2,