0

The following class will not compile under C++11; the loop as it stands can only be executed at runtime and so one gets a "char(*)[i] is a variably-modified type" error from the template class static function call within the loop:

#include <cstddef>
#include <vector>

template <std::size_t N>
class Foo
{
private:
    const std::vector<char(*)[]> bar = bar_init();

    static std::vector<char(*)[]> bar_init()
    {
        std::vector<char(*)[]> init;

        for (size_t i = N; i > 0; i >>= 1)
        {
            auto ptr_to_array = MyClass<char(*)[i]>::static_return_ptr_to_array();
            init.emplace_back(reinterpret_cast<char(*)[]>(ptr_to_array));
        }

        return init;
    }
};

Is there a way I can accomplish the same effect using templates within the initialization function? That is to say, initialize "bar" of size log2(N) at "Foo" class instantiation as a const vector of pointers to array of char with each vector element containing e.g. for N=32 the output of:

MyClass<char(*)[32]>::static_return_ptr_to_array();
MyClass<char(*)[16]>::static_return_ptr_to_array();
MyClass<char(*)[8]>::static_return_ptr_to_array();

//etc...
MattyZ
  • 1,541
  • 4
  • 24
  • 41
  • Possible duplicate of [Create static array with variadic templates](https://stackoverflow.com/questions/6060103/create-static-array-with-variadic-templates) – underscore_d Oct 26 '17 at 12:07
  • 2
    `index_sequence` might help. – Jarod42 Oct 26 '17 at 12:11
  • 1
    Can you show us your `MyClass` ? Or a simplified (but compilable) version of it? – max66 Oct 26 '17 at 12:17
  • @underscore_d The question there seems partly applicable, but in my situation my vector is only const, not const static, each instantiation of "Foo" may be templated with different values of N so the initialization procedure for bar would seemingly require a mixture of both template specialization and runtime calls. – MattyZ Oct 26 '17 at 12:17
  • @max66 I altered the code I posted somewhat for the example to make it simpler to understand the issue I was encountering. In the actual code the templated static method call returns an allocator, which must be templated on a certain type, in this case an array of char of some size. The allocator's allocate method is then called and gives up a pointer to array of char, and that output is what I'd like to put in the vector. – MattyZ Oct 26 '17 at 12:23
  • @Bitrex What is the allocator? A plain function? or some sort of object? If a function, do all the allocators have a fixed signature? If an object, can the allocators derive from a common base (and make the allocation function be virtual?) – Martin Bonner supports Monica Oct 26 '17 at 12:27
  • @Bitrex - sorry but if you ask a question of type "why this code didn't work", you should show a minimal but full compilable code. Without it, it's really difficult help you and, anyway, your question is off topic. – max66 Oct 26 '17 at 12:28
  • @max66 I didn't believe it was relevant to the question where the pointers to array came from, specifically, and thought the issue I was encountering was clear from the snippet I posted. If the answer is "it's not possible" then that's fine. Off topic? That's absurd. – MattyZ Oct 26 '17 at 12:41
  • @max66 is right, std::vector requires a complete type ( you may use boost::vector as a workaround, but why not using a char** ? ); as it is, the code is not portable – Massimiliano Janes Oct 26 '17 at 12:43
  • @MassimilianoJanes, Sure, char** would be fine. – MattyZ Oct 26 '17 at 12:45
  • @Bitrex - "Off topic" aren't my words: if you follow "close" and "off topic because", you find what I've said. I suppose the answer is "it's possible", but you should prepare an example that is more simple to test/modify – max66 Oct 26 '17 at 12:46
  • @max66 If I knew how to write a full working example which could be successfully compiled, tested, and modified I wouldn't be asking the question... – MattyZ Oct 26 '17 at 12:53

3 Answers3

2

something like ( in c++11 )

template<int I> 
struct tag{};

void init( std::vector<char(*)[]>& result, tag<0> ){}

template<int I>
void init( std::vector<char(*)[]>& result, tag<I> )
{
    auto ptr_to_array = MyClass<char(*)[I]>::static_return_ptr_to_array;

    result.emplace_back(reinterpret_cast<char(*)[]>(ptr_to_array));

    init(result,tag<(I>>1)>{});
}

template <std::size_t N>
class Foo
{
private:
    const std::vector<char(*)[]> bar = bar_init();

    static std::vector<char(*)[]> bar_init()
    {
        std::vector<char(*)[]> result;

        init( result, tag<N>{} );

        return result;
    }
};

in c++17 this can be further simplified with an if constexpr and no tag<>. Moreover, note that std::vector<char(*)[]> is not portable because vector needs a complete type.

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
1

I think the crucial insight here, is that you can't write:

int i = ?? // automatic variable
auto val = MyClass<char(*)[i]>::static_return_ptr_to_array()

... template arguments have to be constants.

What you can do is something like:

const std::unordered_map<int,???> allocator_map = {
    {1, MyClass<char(*)[1]>::static_return_ptr_to_array},
    {2, MyClass<char(*)[2]>::static_return_ptr_to_array},
    {4, MyClass<char(*)[4]>::static_return_ptr_to_array},
    {8, MyClass<char(*)[8]>::static_return_ptr_to_array},
    ...
};

and then

const auto it = allocator_map.find(i);
if (it == allocator_map.end())
    // throw error
auto val = (it->second)();

Basically, the idea is that you have a static array of allocator functions, and then index into it. (There may be clever ways of using templates to initialize the map. I probably would just write it out by hand though - possibly using a preprocessor macro).

1

If you define index container type traits (or you use std::index_sequence, unfortunately available only starting from C++14)

template <std::size_t ...>
struct indexList
 { };

and you define a type traits to extract a sequence of decreasing power of two

template <std::size_t, typename>
struct iLH;

template <std::size_t N, std::size_t ... Is>
struct iLH<N, indexList<Is...>> : public iLH<(N >> 1), indexList<Is..., N>>
 { };

template <std::size_t ... Is>
struct iLH<0U, indexList<Is...>>
 { using type = indexList<Is...>; };

template <std::size_t N>
struct getIndexList : public iLH<N, indexList<>>
 { };

template <std::size_t N>
using getIndexList_t = typename getIndexList<N>::type;

should be possible write your Foo simply as

template <std::size_t N>
class Foo
 {
   private:
      const std::vector<char **> bar = bar_init (getIndexList_t<N>{});

      template <std::size_t ... Is>
      static std::vector<char **> bar_init (indexList<Is...> const &)
       {
         std::vector<char **> init { MyClass<char(*)[Is]>::getPtr()... };

         return init;
       }
 };

(supposing a static getPtr() method in MyClass) that return a char ** and a bar vector of char **).

The following is a full compiling example

template <typename T>
struct MyClass;

template <std::size_t Dim>
struct MyClass<char(*)[Dim]>
 {
   static char ** getPtr ()
    { static char ach[Dim]; static char * ret { ach } ; return &ret; }
 };

template <std::size_t ...>
struct indexList
 { };

template <std::size_t, typename>
struct iLH;

template <std::size_t N, std::size_t ... Is>
struct iLH<N, indexList<Is...>> : public iLH<(N >> 1), indexList<Is..., N>>
 { };

template <std::size_t ... Is>
struct iLH<0U, indexList<Is...>>
 { using type = indexList<Is...>; };

template <std::size_t N>
struct getIndexList : public iLH<N, indexList<>>
 { };

template <std::size_t N>
using getIndexList_t = typename getIndexList<N>::type;

template <std::size_t N>
class Foo
 {
   private:
      const std::vector<char **> bar = bar_init (getIndexList_t<N>{});

      template <std::size_t ... Is>
      static std::vector<char **> bar_init (indexList<Is...> const &)
       {
         std::vector<char **> init { MyClass<char(*)[Is]>::getPtr()... };

         return init;
       }
 };

int main ()
 {
   Foo<32U>  f32;
 }
max66
  • 65,235
  • 10
  • 71
  • 111