c++17 making std::variant by generating cartesian product of pre-declared lists of types
Asked Answered
B

1

6

Say I have a class of three template type parameters.

template<typename Transformer, typename Criteria, typename Strategy>
struct ConfiguredPipeline {};

And have the following classes to be used later in instantiating ConfiguredPipeline:

template<typename...>
struct CriteriaList
{
};
using SupportedCriteria = CriteriaList<ChiSquared, Cosine>;

template<typename...>
struct StrategiesList
{
};
using SupportedStrategies = StrategiesList<Voting>;

template<typename...>
struct TransformerList
{
};
using SupportedTransformer = TransformerList<AAGrouper11, AAGrouper15>;

How would I be able to generate an std::variant equivalent to:

using PipelineVariant = std::variant< 
ConfiguredPipeline< ChiSquared , Voting , AAGrouper11 > ,
ConfiguredPipeline< ChiSquared , Voting , AAGrouper15 > ,
ConfiguredPipeline< Cosine , Voting , AAGrouper11 > ,
ConfiguredPipeline< Cosine , Voting , AAGrouper15 >>;

By simple invoking a function like:

using PipelineVariant = makeVariant< ConfiguredPipeline , SupportedCriteria , SupportedStrategies, SupportedTransformaers >;
Boreas answered 5/8, 2018 at 16:56 Comment(0)
O
8

I suppose that decltype(), std::declval() and std::tuple_cat() can help you a lot.

Given the following couple of overloaded template declared (observe: declared, not defined, following the std::declval() example) functions to make the cartesian products of type and to collapse (thanks to std::tuple_cat()) in a single std::tuple

template <template <typename...> class C, typename ... Ts>
constexpr std::tuple<C<Ts...>> tupleExpand (std::tuple<Ts...> const &);

template <template <typename...> class C, typename ... Ts,
          template <typename...> class C0, typename ... Ls,
          typename ... Cs>
constexpr auto tupleExpand (std::tuple<Ts...> const &, C0<Ls...> const &,
                            Cs const & ... cs)
   -> decltype(std::tuple_cat(
         tupleExpand<C>(std::declval<std::tuple<Ts..., Ls>>(), cs...)...));

and the following simple template function (again: only declared) to convert a std::tuple type with a list of types in a corresponding std::variant list of types

template <typename ... Ts>
constexpr std::variant<Ts...> tupleToVariant (std::tuple<Ts...> const &);

you can write a MakeVariant class as follows

template <template <typename...> class C, typename ... Ts>
struct MakeVariant
 {
   using type = decltype(tupleToVariant(std::declval<
                   decltype(tupleExpand<C>(std::declval<std::tuple<>>(),
                                           std::declval<Ts>()...))>()));
 };

and with a simple using helper

template <template <typename...> class C, typename ... Ts>
using MakeVariantType = typename MakeVariant<C, Ts...>::type;

you can define your PipelineVariant type as follows

using PipelineVariant = MakeVariantType<ConfiguredPipeline,
                                        SupportedCriteria,
                                        SupportedStrategies,
                                        SupportedTransformers>;

The following is a full compiling example

#include <tuple>
#include <variant>

template <typename, typename, typename>
struct ConfiguredPipeline
 { };

struct ChiSquared {};
struct Cosine     {};

template <typename...>
struct CriteriaList
 { };

using SupportedCriteria = CriteriaList<ChiSquared, Cosine>;

struct Voting {};

template <typename...>
struct StrategiesList
 { };

using SupportedStrategies = StrategiesList<Voting>;

struct AAGrouper11 { };
struct AAGrouper15 { };

template <typename...>
struct TransformerList
 { };

using SupportedTransformers = TransformerList<AAGrouper11, AAGrouper15>;

template <template <typename...> class C, typename ... Ts>
constexpr std::tuple<C<Ts...>> tupleExpand (std::tuple<Ts...> const &);

template <template <typename...> class C, typename ... Ts,
          template <typename...> class C0, typename ... Ls,
          typename ... Cs>
constexpr auto tupleExpand (std::tuple<Ts...> const &, C0<Ls...> const &,
                            Cs const & ... cs)
   -> decltype(std::tuple_cat(
         tupleExpand<C>(std::declval<std::tuple<Ts..., Ls>>(), cs...)...));

template <typename ... Ts>
constexpr std::variant<Ts...> tupleToVariant (std::tuple<Ts...> const &);

template <template <typename...> class C, typename ... Ts>
struct MakeVariant
 {
   using type = decltype(tupleToVariant(std::declval<
                           decltype(tupleExpand<C>(std::declval<std::tuple<>>(),
                                                   std::declval<Ts>()...))>()));
 };

template <template <typename...> class C, typename ... Ts>
using MakeVariantType = typename MakeVariant<C, Ts...>::type;

using PipelineVariant = MakeVariantType<ConfiguredPipeline,
                                        SupportedCriteria,
                                        SupportedStrategies,
                                        SupportedTransformers>;

int main () 
 {
   static_assert(std::is_same<PipelineVariant, 
                              std::variant<ConfiguredPipeline<ChiSquared, Voting, AAGrouper11>,
                                           ConfiguredPipeline<ChiSquared, Voting, AAGrouper15>,
                                           ConfiguredPipeline<Cosine, Voting, AAGrouper11>,
                                           ConfiguredPipeline<Cosine, Voting, AAGrouper15>>>::value);
 }
Othilia answered 5/8, 2018 at 21:5 Comment(3)
Thank you! It did save me from writing 3*3*2 template combinations (and expected to grow) in my current implementation.Boreas
@AAlaa Ouch your compile time. Are you sure you can't do it using function pointers or virtual functions?Whomp
@Whomp other solutions make run-time debugging very difficult in my caseBoreas

© 2022 - 2024 — McMap. All rights reserved.