3

I am trying to use std::integer_sequence and its helper template std::index_sequence to initialize a fixed size POD struct that behaves like a container. Each element in turn contains a fixed size array field.

The container POD struct is essentially defined as:

#define ELEM_NAME_SIZE   7
#define CONTAINER_SIZE  20

using Container = struct Container {
    int entries;
    Element elems[MAX_CONTAINER_SIZE];    
};

and the individual elements are:

using Element = struct Element {
    char name[MAX_NAME_CHARS];
    bool bFlag;
};

Building on the answer to question, I was able to use std::index_sequence to initialize the fixed length Elements. So far so good.

I need to come up with a way to construct the Container with either a single fixed size const char* - say "ABCDEF", or alternatively with an array of fixed length (up to a max of ELEM_NAME_SIZE) character strings.

constexpr char gTestNames[3][ELEM_NAME_SIZE] = {
    "APPLE", "BEE", "CHAIN"
};

In the live coliru code, the first of these container constructors is as follows:

template<std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr Container makeContainer(char const (&name)[N]) {
    return makeContainer_in(name, Indices{});
}

constructing and printing out the contents of the container with this yield:

const auto container = makeContainer("ABCDEF");
std::cout << container << '\n';

with the output:

Container: entries(7)[Element:[ABCDEF],,Element:[],,Element:[],,Element:[],,Element:[],,Element:[],,Element:[],,]

However, using the alternative template function overload, with parameter gTestNames:

template<std::size_t N, std::size_t NAME_LEN_MAX = ELEM_NAME_SIZE, typename Indices = std::make_index_sequence<N>>
constexpr Container makeContainer(const char(&names)[N][NAME_LEN_MAX]) {
    return makeContainer_in(names, Indices{});
}

Called using:

const auto container1 = makeContainer(gTestNames);
std::cout << container1 << '\n';;

I get the following error output:

main.cpp: In instantiation of 'constexpr Container makeContainer(const char (&)[N][NAME_LEN_MAX]) [with long unsigned int N = 3; long unsigned int NAME_LEN_MAX = 7; Indices = std::integer_sequence<long unsigned int, 0, 1, 2>; Container = Container]':
main.cpp:78:53:   required from here
main.cpp:64:28: error: no matching function for call to 'makeContainer_in(const char [3][7], std::integer_sequence<long unsigned int, 0, 1, 2>)'
   64 |     return makeContainer_in(names, Indices{});
      |            ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
main.cpp:52:21: note: candidate: 'template<long unsigned int N, long unsigned int ...Is> constexpr Container makeContainer_in(const char (&)[N], std::index_sequence<Is ...>)'
   52 | constexpr Container makeContainer_in(char const (&packed)[N], std::index_sequence<Is...>) {
      |                     ^~~~~~~~~~~~~~~~
main.cpp:52:21: note:   template argument deduction/substitution failed:
main.cpp:64:28: note:   mismatched types 'const char' and 'const char [7]'
   64 |     return makeContainer_in(names, Indices{});
      |            ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~

If possible, besides correcting the mistake I have, is there some way I could simplify all this boiler plate code - perhaps using fold expressions - I would like to use index_sequences (as I am trying to learn how to use them), but I never understood the requirement to always forward the calls to a sort of proxy in order to expand the indices. There has got to be an easier way.

johnco3
  • 2,401
  • 4
  • 35
  • 67
  • What is the point of `using Var = struct Var {};`? Why not just `struct Var`? – KamilCuk Jul 01 '20 at 22:23
  • @KamilCuk See the full coliru where I have diagnostic friend printer - that is why I indicated 'essentially' hello again my friend :) - as you can see I am trying to learn from your guidance – johnco3 Jul 01 '20 at 22:35
  • 1
    `using Container = struct Container {` please don't do this. Where did you see this? – bolov Jul 01 '20 at 22:53
  • I needed a forward reference for a friend fn that is shown in the coliru project and not in the question – johnco3 Jul 01 '20 at 22:59
  • `I needed a forward reference for a friend` Makes no sense. `using` is not a forward reference. `struct Container;` would be a forward reference. – KamilCuk Jul 01 '20 at 23:37
  • You need the “proxy” because only a *template-head* can introduce a template parameter pack. The syntax is unfortunate but inevitable. – Davis Herring Jul 02 '20 at 00:16
  • @DavisHerring - I was really hoping this could be simplified by fold expressions or something - but I guess not - by template-head - I take it you mean the one that has `typename Indices = std::make_index_sequence` which in turns forwards to the `impl` one for want of a better name – johnco3 Jul 02 '20 at 00:40
  • 1
    @johnco3: A *template-head* is the syntactic component that looks like `template<…>`; since you can’t deduce from a default argument, the caller must have explicitly constructed a `std::integer_sequence`, which makes that caller be a wrapper for ergonomic reasons. – Davis Herring Jul 02 '20 at 01:27

2 Answers2

1

Well of course:

template<std::size_t N, std::size_t... Is>
constexpr Container makeContainer_in(char const (&packed)[N], std::index_sequence<Is...>) {
     return Container{ N, { packed[Is]... } };
}

is not going to work, a Container has an Element, not chars (and an Element has no Element::Element(char) constructor). Just instantiate the actual container element using it's constructor inside the parameter pack.

template<std::size_t N, std::size_t... Is>
constexpr Element makeElement_in(char const (&name)[N], std::index_sequence<Is...>) {
    return Element{ { name[Is]... }, true, };
}

template<std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr Element makeElement(char const (&name)[N]) {
    return makeElement_in(name, Indices{});
}

template<std::size_t N, std::size_t NAME_LEN_MAX, std::size_t... Is>
constexpr Container makeContainer_in(const char(&names)[N][NAME_LEN_MAX], std::index_sequence<Is...>) {
    return Container{ N, { makeElement(names[Is])... } };
}

template<std::size_t N, std::size_t NAME_LEN_MAX, typename Indices = std::make_index_sequence<N>>
constexpr Container makeContainer(const char(&names)[N][NAME_LEN_MAX]) {
    return makeContainer_in(names, Indices{});
}

Godbolt link.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • with makeContainer_in adjusted to include missing flag parameter template function compiles, viz `template constexpr Container makeContainer_in(char const (&packed)[N], std::index_sequence) { return Container{ N, { packed[Is]..., false } }; }` however the results are not quite what I expected as I got Container: entries(6)[Element:[HELLO],,Element:[],,Element:[],,Element:[],,Element:[],,Element:[],,] and I was trying to create Element:[H], Element:[E], Element:[L], Element:[L], Element:[O], looks like I need the pack expansion outside Element – johnco3 Jul 01 '20 at 23:05
  • Aha!!!! `template constexpr Container makeContainer_in(char const (&packed)[N], std::index_sequence) { return Container{ N, {{ packed[Is], false }...} }; }` gives: `Container: entries(6)[Element:[H],,Element:[E],,Element:[L],,Element:[L],,Element:[O],,Element:[],,]` note the extra braces around the element to make sure expansion occurs with entire element rather than expanding HELLO for the first element – johnco3 Jul 01 '20 at 23:11
1

if you want the output following output given HELLO

 Element:[H], Element:[E], Element:[L], Element:[L], Element:[O],

this code will work, although there will be an element for the null character if you pass a null terminated string.

#include <iostream>
#include <tuple>

#define MAX_NAME_CHARS 9
#define MAX_CONTAINER_SIZE 100

struct Element {
    char name[MAX_NAME_CHARS];
    bool bFlag;
    int foo;
    friend std::ostream& operator << (std::ostream& os, const Element& next) {
        os << next.name;
        return os;
    }    
};

struct Container {
    int entries;
    Element elems[MAX_CONTAINER_SIZE];    
    friend std::ostream& operator << (std::ostream& os, const Container& next) {        
            os << "Container: entries(" << next.entries << ")[";
            for (auto i = 0; i<next.entries; ++i) {
                os << next.elems[i] << ",";
            }
            os << "]\n";
            return os;
    } 
};

template<std::size_t N, std::size_t ... I>
constexpr Container makeContainerInSingle(const char(&singlecharnames)[N],
                                 std::index_sequence<I...>) {
    auto result = Container {
        N,
        {Element{
            {singlecharnames[I]},
            true, 
            0}...
        }
    };
    return result;
}

template<std::size_t N>
constexpr Container makeContainerSingle(const char(&singlecharnames)[N]) {
    return makeContainerInSingle(singlecharnames, std::make_index_sequence<N>{});
}

int main() {
    auto c2 = makeContainerSingle("HELLO");
    std::cout << c2 << std::endl;
}

output is the following

Container: entries(6)[H,E,L,L,O,,]

as an additional note, im not sure what, if anything, can be done to simplify the pattern using std::index_sequence although the index sequence can be constructed directly as in my example code if you consider that more simple.

  • 1
    @UnforseenPrincess thanks, I actually figured it out `http://coliru.stacked-crooked.com/a/c94182b9ad3d73be` when I realized I did not properly expand the element and instead expanded the element's name field. I also realized that I had to adjust `template constexpr Container makeContainer_in(char const (&packed)[N], std::index_sequence) { return Container{ N-1, {{ packed[Is], false }...} }; }` N to N-1 as N includes the terminating zero of the initial const char* – johnco3 Jul 02 '20 at 00:25
  • 1
    @UnforseenPrincess +1 for the `std::make_index_sequence{}` that saves quite a bit of boiler plate code and makes it a little more readable in my opinion. Thanks - good answer – johnco3 Jul 02 '20 at 23:45