0

I'm trying to create (at compile time) a structure of structs based on some enum values. I'll write the code, it's easier to explain:

enum class EnumTest {A,B,C,D,E,F};

template<class EnumType, EnumType enumValue>
struct MappedStruct
  {
  int element = 0;
  };

template<class EnumType, EnumType enumValue>
struct VecStruct
  {
  std::vector<MappedStruct<EnumType, enumValue>> vec;
  };


template<class EnumType, EnumType... enumValues>
class MapperSelection
  {
  public:

    template<EnumType type>
    void insert(int arg) {mapper_<EnumType, type>.vec.push_back(arg); // * compilation error here *
 }

  private:
    VecStruct<EnumType, enumValues...> mapper_;
  };
int main()
{
MapperSelection< EnumTest::A, EnumTest::B, EnumTest::C> selection;
selection.insert<EnumTest::>(100);
return 0;
}

Is there any way to make compile this code? or some workaround to have the same behavior? possibly with all the enums?

user3770392
  • 453
  • 5
  • 12

1 Answers1

1

It's not fully clear to me, but what I think you want is something like this:

#include <vector>
#include <ostream>
#include <tuple>
#include <iostream>

struct A { static constexpr const char* name() { return "A"; }};
struct B { static constexpr const char* name() { return "B"; }};
struct C { static constexpr const char* name() { return "C"; }};

template<class Tag>
struct tagged_vector : std::vector<int>
{
    using std::vector<int>::vector;
};

template<class...Tags>
struct selection
{
    using storage_type = std::tuple<tagged_vector<Tags>...>;

    template<class Tag>
    void add(Tag, int value)
    {
        std::get<tagged_vector<Tag>>(storage_).push_back(value);
    }

    template<std::size_t I>
    static const char* make_sep(std::integral_constant<std::size_t, I>) {
        return "\n";
    }

    static const char* make_sep(std::integral_constant<std::size_t, 0>) {
        return "";
    };

    template<class Tag>
    static void impl_report(std::ostream& os, tagged_vector<Tag> const& vec, const char* sep)
    {
        os << sep << Tag::name() << " ";
        std::copy(std::begin(vec), std::end(vec), std::ostream_iterator<int>(os, ", "));
    }

    template<class Tuple, std::size_t...Is>
    static void impl_report(std::ostream& os, Tuple&& tup, std::index_sequence<Is...>)
    {
        using expand = int[];
        void(expand{0,
                    (impl_report(os, std::get<Is>(tup), make_sep(std::integral_constant<std::size_t, Is>())), 0)...
        });
    };

    std::ostream& report(std::ostream& os) const {

        impl_report(os, storage_, std::make_index_sequence<std::tuple_size<storage_type>::value>());
        return os;
    }


    storage_type storage_;
};

int main()
{

    auto sel = selection<A, B, C>();

    sel.add(A(), 100);
    sel.add(A(), 101);
    sel.add(B(), 90);
    sel.add(C(), 10);
    sel.add(C(), 11);

    sel.report(std::cout) << std::endl;
}

expected output:

A 100, 101, 
B 90, 
C 10, 11, 

Whether the tags are types or enums (probably) doesn't matter, but types will make life easier if you can live with them.

Update:

reimplemented with enums. Note that it's a little less tidy and requires more knowledge on the user's part. This is normally considered a bad thing in c++ design:

#include <vector>
#include <ostream>
#include <tuple>
#include <iostream>

enum class my_tag
{
 A,B,C
};

const char* name(my_tag t)
{
    switch(t) {
        case my_tag ::A:
            return "A";
        case my_tag ::B:
            return "B";
        case my_tag ::C:
            return "C";
    }
    return "logic error";
}

template<class TagType, TagType TagValue>
struct tagged_vector : std::vector<int>
{
    using std::vector<int>::vector;
};

template<class TagType, TagType...Tags>
struct selection
{
    using storage_type = std::tuple<tagged_vector<TagType, Tags>...>;

    template<TagType Tag>
    void add(int value)
    {
        std::get<tagged_vector<TagType, Tag>>(storage_).push_back(value);
    }

    template<std::size_t I>
    static const char* make_sep(std::integral_constant<std::size_t, I>) {
        return "\n";
    }

    static const char* make_sep(std::integral_constant<std::size_t, 0>) {
        return "";
    };

    template<TagType Tag>
    static void impl_report(std::ostream& os, tagged_vector<TagType, Tag> const& vec, const char* sep)
    {
        os << sep << name(Tag) << " ";
        std::copy(std::begin(vec), std::end(vec), std::ostream_iterator<int>(os, ", "));
    }

    template<class Tuple, std::size_t...Is>
    static void impl_report(std::ostream& os, Tuple&& tup, std::index_sequence<Is...>)
    {
        using expand = int[];
        void(expand{0,
                    (impl_report(os, std::get<Is>(tup), make_sep(std::integral_constant<std::size_t, Is>())), 0)...
        });
    };

    std::ostream& report(std::ostream& os) const {

        impl_report(os, storage_, std::make_index_sequence<std::tuple_size<storage_type>::value>());
        return os;
    }


    storage_type storage_;
};

int main()
{

    auto sel = selection<my_tag, my_tag::A, my_tag::B, my_tag::C>();

    sel.add<my_tag::A>(100);
    sel.add<my_tag::A>(101);
    sel.add<my_tag::B>(90);
    sel.add<my_tag ::C>(10);
    sel.add<my_tag ::C>(11);

    sel.report(std::cout) << std::endl;

}

Updated for c++11 ghastliness:

#include <vector>
#include <ostream>
#include <tuple>
#include <iostream>

namespace notstd  // WARNING: at own risk, otherwise use own namespace
{
    template <size_t... Ints>
    struct index_sequence
    {
        using type = index_sequence;
        using value_type = size_t;
        static constexpr std::size_t size() noexcept { return sizeof...(Ints); }
    };

    // --------------------------------------------------------------

    template <class Sequence1, class Sequence2>
    struct _merge_and_renumber;

    template <size_t... I1, size_t... I2>
    struct _merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
        : index_sequence<I1..., (sizeof...(I1)+I2)...>
    { };

    // --------------------------------------------------------------

    template <size_t N>
    struct make_index_sequence
        : _merge_and_renumber<typename make_index_sequence<N/2>::type,
                              typename make_index_sequence<N - N/2>::type>
    { };

    template<> struct make_index_sequence<0> : index_sequence<> { };
    template<> struct make_index_sequence<1> : index_sequence<0> { };

    template <class T, class Tuple>
    struct Index;

    template <class T, class... Types>
    struct Index<T, std::tuple<T, Types...>> {
        static const std::size_t value = 0;
    };

    template <class T, class U, class... Types>
    struct Index<T, std::tuple<U, Types...>> {
        static const std::size_t value = 1 + Index<T, std::tuple<Types...>>::value;
    };
}

enum class my_tag
{
 A,B,C
};

const char* name(my_tag t)
{
    switch(t) {
        case my_tag ::A:
            return "A";
        case my_tag ::B:
            return "B";
        case my_tag ::C:
            return "C";
    }
    return "logic error";
}

template<class TagType, TagType TagValue>
struct tagged_vector : std::vector<int>
{
    using std::vector<int>::vector;
};

template<class TagType, TagType...Tags>
struct selection
{
    using storage_type = std::tuple<tagged_vector<TagType, Tags>...>;

    template<TagType Tag>
    void add(int value)
    {
        using index = notstd::Index<tagged_vector<TagType, Tag>, storage_type>;
        std::get<index::value>(storage_).push_back(value);
    }

    template<std::size_t I>
    static const char* make_sep(std::integral_constant<std::size_t, I>) {
        return "\n";
    }

    static const char* make_sep(std::integral_constant<std::size_t, 0>) {
        return "";
    };

    template<TagType Tag>
    static void impl_report(std::ostream& os, tagged_vector<TagType, Tag> const& vec, const char* sep)
    {
        os << sep << name(Tag) << " ";
        std::copy(std::begin(vec), std::end(vec), std::ostream_iterator<int>(os, ", "));
    }

    template<class Tuple, std::size_t...Is>
    static void impl_report(std::ostream& os, Tuple&& tup, notstd::index_sequence<Is...>)
    {
        using expand = int[];
        void(expand{0,
                    (impl_report(os, std::get<Is>(tup), make_sep(std::integral_constant<std::size_t, Is>())), 0)...
        });
    };

    std::ostream& report(std::ostream& os) const {

        impl_report(os, storage_, notstd::make_index_sequence<std::tuple_size<storage_type>::value>());
        return os;
    }


    storage_type storage_;
};

int main()
{

    auto sel = selection<my_tag, my_tag::A, my_tag::B, my_tag::C>();

    sel.add<my_tag::A>(100);
    sel.add<my_tag::A>(101);
    sel.add<my_tag::B>(90);
    sel.add<my_tag ::C>(10);
    sel.add<my_tag ::C>(11);

    sel.report(std::cout) << std::endl;

}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • very interesting solution! yes, I'm using enums, so I have to slightly modify this to make it work with them. How about performances? – user3770392 Mar 21 '17 at 15:36
  • @user3770392 it's all resolved at compile time. There will be no performance overhead when compiled with optimisations turned on. – Richard Hodges Mar 21 '17 at 15:48
  • that's awesome.. one last question: implementing that for enums like in my case, would it possible to do something like: using storage_type = std::tuple< Mapper...>; ? For instance if I use 5 enum values, will this create a tuple of 5 Mappers? – user3770392 Mar 21 '17 at 15:56
  • Yes, except EnumType would probably be a class template argument rather than a template argument in storage_type (where it's already implied) – Richard Hodges Mar 21 '17 at 16:05
  • @user3770392 for completeness, answer updated to use enum. Note the more complex constructor and the need for a free function to name the tag values. – Richard Hodges Mar 21 '17 at 16:16
  • Thank you very much. Yes, I've noticed that. The only problem is.. I'm using C++11, so the get<> magic thing doesn't work in my case. Is there any easy workaround? – user3770392 Mar 21 '17 at 16:32
  • it's not get<> that's your problem it's std::make_index_sequence. index sequence for c++11 implementation here: http://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence – Richard Hodges Mar 21 '17 at 16:34
  • @user3770392 and why are you using c++11? It's ancient. Upgrade your compiler. If your boss/teacher is standing in the way of progress, educate him with a baseball bat. – Richard Hodges Mar 21 '17 at 16:36
  • I've commented out all the report related part, so make_index_sequence too, and I still get: no matching function to call to 'get< ...... – user3770392 Mar 21 '17 at 16:37
  • @user3770392 sec - will rewrite for c++11 – Richard Hodges Mar 21 '17 at 16:38
  • yes it's something that I'll do in the future, but at the moment I can't use that. Cheers Richard – user3770392 Mar 21 '17 at 16:38
  • I think that an alternative to the enum specialization would be to use the first version and pass std::integral_constant as input in the main, or maybe provide some helper / wrapper that does this in an automatic way – user3770392 Mar 21 '17 at 16:40
  • @user3770392 missing c++14 functionality added in the notstd:: namespace in 3rd iteration. Note the change in member function `add<>` – Richard Hodges Mar 21 '17 at 16:46
  • awesome post mate! this would be really useful to many people! – user3770392 Mar 21 '17 at 16:57
  • Richard: if you're still here, could you give me some hints to understand the part where you use expand.. why the void? – user3770392 Mar 22 '17 at 10:18
  • Casting to void tells the compiler that you're not going to use the array you just created. It avoids an "unused expression" warning. We just want the side effects of the array creation, not the array of it's itself. – Richard Hodges Mar 22 '17 at 10:20