Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a compile-time static class type that initializes a member container with specific values?

This is basically what I want:

struct Item{
   int id;
   std::string s;   
};

template <???>
struct StaticContainer
{
    static const std::deque<Item> _items;
};

template <???>
const std::deque<Item> StaticContainer<>::_items {???};

Doesn't have to be a deque, I just want to associate an iterable list of values with a type. So that I can do stuff like

typedef StaticContainer<{{1, "a", {2, "b"}, {3, "c"}}> T1;
typedef StaticContainer<{{0, "e"}, {-1, "f"}, {-2, "g"}}> T2;

int main() {
   T1 t1;
   T2 t2;
   t1 = t2; // Error: incompatible types

   return 0;
}

Usually it's making things dynamic that's the problem, but apparently it's just as hard to to make some dynamic things to be compile-time. I do not want to use inheritance, polymorphism and similar runtime, overhead-inducing approaches.

like image 220
Violet Giraffe Avatar asked Jun 18 '16 13:06

Violet Giraffe


2 Answers

You cannot have a compile-time list of user-defined structs. And you cannot have a compile-time std::string of any kind. It is not a literal type, and therefore cannot be used in any constexpr context.

If you restrict yourself to types that can be used in non-type template parameters, then you can use a variadic template type. And then, you don't have to bother with the runtime container:

template<typename T, T ...ts>
struct value_list
{
    //Not the most efficient way to get a value.
    template<int ix>
    static constexpr auto get()
    { return std::get<ix>(std::make_tuple(ts...)); }

    //Get a general container
    template<typename Container>
    static auto get_container() {return Container{ts...};}

    //Get an array, appropriately sized.
    static constexpr auto get_array()
    { return std::array<T, sizeof...(ts)>{ts...}; }

    //Manipulators. Types are static, so they must return
    //new types with the new values.
    template<T new_val>
    constexpr auto push_back()
    {return value_list<T, ts..., new_val>();}

    template<T new_val>
    constexpr auto push_front()
    {return value_list<T, new_val, ts...>();}
};

Live example.

However, be advised that compilers have fairly strict limits on the number of template parameters a type can have. You probably won't blow past this limit by typing them in on a keyboard, but you can construct them past it.

like image 88
Nicol Bolas Avatar answered Oct 18 '22 12:10

Nicol Bolas


What about using variadic template non type arguments?

Something like

--- edit: corrected (thanks m.s.) ---

--- edit2: added array _items2---

#include <deque>
#include <array>
#include <iostream>

struct Item{
   int id;   
};

template <int ... vals>
struct StaticContainer
{
    static const std::deque<Item> _items;
    static const std::array<Item, sizeof...(vals)> _items2;
};

template <int ... vals>
const std::deque<Item> StaticContainer<vals...>::_items { {vals}... };

template <int ... vals>
const std::array<Item, sizeof...(vals)> StaticContainer<vals...>::_items2 { { {vals} ... } };

typedef StaticContainer<1, 2, 3> T1;
typedef StaticContainer<0, -1, -2> T2;

int main ()
 {
   T1 t1;
   T2 t2;
   //t1 = t2; // Error: incompatible types

   std::cout << T1::_items[1].id << std::endl;
   std::cout << T2::_items2[0].id << std::endl;

   return 0;
 }
like image 37
max66 Avatar answered Oct 18 '22 12:10

max66