6

I can write this and it works perfectly fine:

struct Foo
{
  int i;
  std::string s;
};

const Foo foo[] = {
  { 42, "the answer to the ultimate questions" },
  { 23 /*initializing only the first member, 's' gets the default value*/ }
};

What I want to do is to have a struct wrapping the array so I can add methods to it:

template<typename V1, typename V2, size_t Count>
struct Map
{
  std::array<std::pair<V1, V2>, Count> mappings;
  //or
  //std::pair<V1, V2> mappings[Count];

  V1 operator()(const V2&) const;
  V2 operator()(const V1&) const;
};

And I want to initialize it as an array of unknown bound like this:

constexpr Map<int, std::string_view, /*???*/> = {
  { 42, "the answer to the ultimate question" },
  { 23, "some other stuff" },
  { /*...*/ }
};

But then a problem arises that you need to specify the Count template parameter which I don't want to do, I want it to work like in the array case.

I thought that a function returning such object would do the trick, like this:

template<typename V1, typename V2, typename... Args>
constexpr auto makeMap(Args... args)
{
  return Map<V1, V2, sizeof...(Args)>{ args... };
}

which then allows to use it like this:

using Item = std::pair<int, std::string_view>;

constexpr auto map = makeMap<int, std::string_view>(
  Item{ 42, "the answer to the ultimate questions" },
  Item{ 23, "some other stuff" }
);

But if you omit the Item type, then template instantiation can not deduce argument types, which prohibits the usage that I originally wanted:

constexpr auto map = makeMap<int, std::string_view>(
  { 42, "the answer to the ultimate questions" },
  { 23, "some other stuff" }
);

Currently I consider it impossible, but wanted to ask anyway in case I'm missing something.

While researching this, I found a proposal which enables precisely what I want.

Anyways, I would love to get any ideas.

paul
  • 448
  • 4
  • 11
  • You add a ctor that takes as input an std::array> so then you could initialize the array with an initializer list – Claudiu Guiman Feb 15 '19 at 16:35
  • @Claudiu Guiman, `std::array` will not be able to deduce `Count` from an initializer list. This: ```C++ template void test(std::array) {} int main() { test({1,2,3}); } ``` gives ``` prog.cc:45:3: error: no matching function for call to 'test' test({1,2,3}); ^~~~ prog.cc:41:6: note: candidate template ignored: couldn't infer template argument 'Count' void test(std::array) {} ``` – paul Feb 15 '19 at 16:43
  • Yeah, you're right - for some weird reason I got it confused with std::vector. Btw, I know it's not what you asked, but maybe you can use std::vector instead of array? – Claudiu Guiman Feb 15 '19 at 16:45
  • @Claudiu Guiman, for runtime initialization — sure. But I want it to work for initialization at compile time using `constexpr`. Though `std::initializer_list` has all members `constexpr`'d in C++17, including `size()`, I couldn't figure out how to make it work to define the `Count` template parameter. – paul Feb 15 '19 at 16:51
  • https://stackoverflow.com/questions/8192185/using-stdarray-with-initialization-lists You can initialize a std::array with two sets of curly braces: std::array strings = {{ "a", "b" }}; – Claudiu Guiman Feb 15 '19 at 16:51
  • @Claudiu Guiman, sure, but the problem is to _not_ specify size and have it be figured out from the number of items in the initializer list. – paul Feb 15 '19 at 16:54
  • Is `Count` arbitrary, or does it have an upper bound ? then just have to generate N overloads `template constexpr auto makeMap(non_deducible_t> p1, non_deducible_t> p2)`. – Jarod42 Feb 15 '19 at 17:02
  • I think your problem could be simplified down to being able to [make this compile](http://coliru.stacked-crooked.com/a/1709652587f35e3e). – YSC Feb 15 '19 at 17:05
  • @Jarod42, your suggestion will work. Though I think it's better to have just arrays with mappings and have free functions to work with instances of such mapping arrays like this: ```template auto map(Mapping (&mappings)[Count], T value)``` – paul Feb 15 '19 at 17:09

2 Answers2

5

Using the proposed to_array:

template<typename V1, typename V2, size_t N>
constexpr auto makeMap(std::pair<V1, V2> const (&a)[N])
{
  return Map<V1, V2, N>{ to_array<std::pair<V1, V2>>(a) };
}

constexpr auto map = makeMap<int, std::string_view>({
  { 42, "the answer to the ultimate question" },
  { 23, "some other stuff" },
  { /*...*/ }
});

If your compiler supports library fundamentals TS v2, you can find an implementation of to_array in header <experimental/array>, inside namespace std::experimental.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • Thank you! It worked for me in this form: ```template constexpr auto makeMap2(std::pair(&&a)[Count]) { return Map{ to_array(a) }; } ``` Great insight! On monday it goes to production! – paul Feb 15 '19 at 17:53
2

Following the Jarod42's suggestion, in a recursive way, I propose a recursive MakeMyMap struct, with a static func() in it that receive a sequence of std::pair<T1, T2> arguments [observe: 42 is the default upper bound for the number of std::pair arguments].

template <typename T1, typename T2, std::size_t Dim>
struct MyMap
 {
   std::array<std::pair<T1, T2>, Dim> map;
 };

template <typename T, std::size_t>
using getTheType = T;

template <typename, typename, typename = std::make_index_sequence<42u>>
struct MakeMyMap;

template <typename T1, typename T2, std::size_t ... Is>
struct MakeMyMap<T1, T2, std::index_sequence<Is...>>
   : public MakeMyMap<T1, T2, std::make_index_sequence<sizeof...(Is)-1u>>
 {
   using MakeMyMap<T1, T2, std::make_index_sequence<sizeof...(Is)-1u>>::func;

   static auto func (getTheType<std::pair<T1, T2>, Is> const & ... ps)
    { return MyMap<T1, T2, sizeof...(Is)>{ { { ps... } } }; }
 };

template <typename T1, typename T2>
struct MakeMyMap<T1, T2, std::index_sequence<>>
 {
   static auto func ()
    { return MyMap<T1, T2, 0u>{ }; }
 };

So you can write

   auto map = MakeMyMap<int, std::string>::func(
      { 42, "the answer to the ultimate questions" },
      { 23, "some other stuff" }
      );

The following is a full compiling (C++14 is enough) example

#include <array>
#include <string>
#include <utility>

template <typename T1, typename T2, std::size_t Dim>
struct MyMap
 {
   std::array<std::pair<T1, T2>, Dim> map;
 };

template <typename T, std::size_t>
using getTheType = T;

template <typename, typename, typename = std::make_index_sequence<42u>>
struct MakeMyMap;

template <typename T1, typename T2, std::size_t ... Is>
struct MakeMyMap<T1, T2, std::index_sequence<Is...>>
   : public MakeMyMap<T1, T2, std::make_index_sequence<sizeof...(Is)-1u>>
 {
   using MakeMyMap<T1, T2, std::make_index_sequence<sizeof...(Is)-1u>>::func;

   static auto func (getTheType<std::pair<T1, T2>, Is> const & ... ps)
    { return MyMap<T1, T2, sizeof...(Is)>{ { { ps... } } }; }
 };

template <typename T1, typename T2>
struct MakeMyMap<T1, T2, std::index_sequence<>>
 {
   static auto func ()
    { return MyMap<T1, T2, 0u>{ }; }
 };

int main ()
 {
   auto map = MakeMyMap<int, std::string>::func(
      { 42, "the answer to the ultimate questions" },
      { 23, "some other stuff" }
      );

   static_assert( std::is_same<decltype(map),
                               MyMap<int, std::string, 2u>>::value, "!" );
 }

Using C++17, you can use std::string_view instead of std::string, you can define constexpr the func() functions so map can be constexpr

   constexpr auto map = MakeMyMap<int, std::string_view>::func(
      { 42, "the answer to the ultimate questions" },
      { 23, "some other stuff" }
      );

and you can also verify that

   static_assert( std::is_same<decltype(map),
                               MyMap<int, std::string_view, 2u> const>::value, "!" );

Using the new C++17 variadic using, you can avoid recursion at all rewiting MakeMyMap as follows

template <typename, typename, typename = std::make_index_sequence<42u>>
struct MakeMyMap;

template <typename T1, typename T2, std::size_t ... Is>
struct MakeMyMap<T1, T2, std::index_sequence<Is...>>
   : public MMM_helper<T1, T2, std::make_index_sequence<Is>>...
 { using MMM_helper<T1, T2, std::make_index_sequence<Is>>::func...; };

where MMM_helper (Make My Map helper) is defined as follows

template <typename, typename, typename>
struct MMM_helper;

template <typename T1, typename T2, std::size_t ... Is>
struct MMM_helper<T1, T2, std::index_sequence<Is...>>
 {
   static constexpr auto func (getTheType<std::pair<T1, T2>, Is> const & ... ps)
    { return MyMap<T1, T2, sizeof...(Is)>{ { { ps... } } }; }
 };

The following is a full C++17, not-recursive, example

#include <array>
#include <string_view>
#include <utility>

template <typename T1, typename T2, std::size_t Dim>
struct MyMap
 {
   std::array<std::pair<T1, T2>, Dim> map;
 };

template <typename T, std::size_t>
using getTheType = T;

template <typename, typename, typename>
struct MMM_helper;

template <typename T1, typename T2, std::size_t ... Is>
struct MMM_helper<T1, T2, std::index_sequence<Is...>>
 {
   static constexpr auto func (getTheType<std::pair<T1, T2>, Is> const & ... ps)
    { return MyMap<T1, T2, sizeof...(Is)>{ { { ps... } } }; }
 };

template <typename, typename, typename = std::make_index_sequence<42u>>
struct MakeMyMap;

template <typename T1, typename T2, std::size_t ... Is>
struct MakeMyMap<T1, T2, std::index_sequence<Is...>>
   : public MMM_helper<T1, T2, std::make_index_sequence<Is>>...
 { using MMM_helper<T1, T2, std::make_index_sequence<Is>>::func...; };

int main ()
 {
   constexpr auto map = MakeMyMap<int, std::string_view>::func(
      { 42, "the answer to the ultimate questions" },
      { 23, "some other stuff" }
      );

   static_assert( std::is_same<decltype(map),
                               MyMap<int, std::string_view, 2u> const>::value, "!" );
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thanks for you suggestion. Interesting idea, though it is limited to 42 parameters (or a prederemined number). – paul Feb 15 '19 at 17:56
  • @paul - unfortunately you're right: an upper limit is requested. I've improved the answer to show a not-recursive (C++17 only) solution. In the not-recursive way, you can avoid the template recursion limit that can be low. But the upper limit requirement remain. – max66 Feb 15 '19 at 18:02