Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ : using index as template parameter in for loop

given following templates and specialization

enum CountryName 
{
    Armenia = 0 ,
    Georgia,
    Size = 2
};

template <CountryName variable>
class CountryInfo;

template <>
class CountryInfo<Armenia> 
{
    /* CODE HERE */
};

template <>
class CountryInfo<Georgia> 
{
    /* CODE HERE */
};

I would like to iterate over enum and create object for each specialization.

main() {
    for(auto i=0; i<CountryName::Size; ++i) {
        CountryInfo<(static_cast<CountryName>(i))>();
    }       
}   

I get following error: error: the value of 'i' is not usable in a constant expression CountryInfo<(static_cast(i))>();

like image 928
libxelar.so Avatar asked Aug 01 '18 12:08

libxelar.so


4 Answers

What you want is convert a run-time variable into a compile-time variable (which is the requirement for a template argument). There are various ways to achieve this, for example

enum struct Country {
    Armenia, Georgia, India
};

template<template<County> class Functor, typename... Args>
void LoopCountries(Args&&...args)
{
    { Functor<Armenia> func; func(std::forward<Args>(args)...); }
    { Functor<Georgia> func; func(std::forward<Args>(args)...); }
    { Functor<India> func; func(std::forward<Args>(args)...); }
}

which assumes that Functor<> has a member operator(). Now you can simply

LoopCountries<CountryInfo>();

A more common situation is to pick one value (instead of looping over all):

template<template<County> class Functor, typename... Args>
void SwitchCountry(Country country, Args&&...args)
{
    switch(country) {
    case Armenia: { Functor<Armenia> func; func(std::forward<Args>(args)...); }
    case Georgia: { Functor<Georgia> func; func(std::forward<Args>(args)...); }
    case India: { Functor<India> func; func(std::forward<Args>(args)...); }
    }
}
like image 171
Walter Avatar answered Sep 28 '22 12:09

Walter


As I said in the comments, templates are resolved at compile-time. I.e. only constant values can be used as template parameters, which the variable i is not.

What you can do is some kind of recursive template iteration:

template<CountryName c>
struct ForLoop {
    template<template <CountryName> class Func>
    static void iterate() {
        ForLoop<static_cast<CountryName>(c - 1)>::template iterate<Func>();
        Func<c>()();
    }
};

//so that compiler knows when to stop
template <>
struct ForLoop<Armenia> {
  template <template <CountryName> class Func>
  static void iterate() {
    Func<Armenia>()();
  }
};

// CountryInfo needs an overloaded ()-operator, whcih get's called in the ForLoop
template <CountryName n>
struct CountryInfo {
  void operator()() { std::cout << n << std::endl; }
};

int main() {
  ForLoop<Georgia>::iterate<CountryInfo>();
  return 0;
}

In the main-function the static ForLoop<Georgia>::iterate-function get's called, this function then substracts 1 from Georgia and calls the function iterate again, until it hits the ForLoop<Armenia>::iterate which is the last function which get's called. In case you have any questions, let me know.

like image 45
Mike van Dyke Avatar answered Sep 28 '22 10:09

Mike van Dyke


As explained by Mike van Dike, a template parameter needs to be known compile-time but your i is modified run time.

You have to use compile-time known indexes.

If you can use C++14, you can use variadic templates, std::make_index_sequence and std::index_sequence so you can do something as follows (see iterateCountry())

#include <tuple>
#include <type_traits>

enum CountryName 
{
    Armenia = 0 ,
    Georgia,
    Size = 2
};

template <CountryName variable>
class CountryInfo;

template <>
class CountryInfo<Armenia> 
{
    /* CODE HERE */
};

template <>
class CountryInfo<Georgia> 
{
    /* CODE HERE */
};

template <std::size_t ... Is>
auto iterateCountry (std::index_sequence<Is...> const &)
 { return std::make_tuple(CountryInfo<static_cast<CountryName>(Is)>{}...); }


int main ()
 {
   auto ict { iterateCountry(
                 std::make_index_sequence<static_cast<std::size_t>(
                    CountryName::Size)>{}) };

   static_assert(std::is_same<decltype(ict),
                              std::tuple<CountryInfo<Armenia>,
                                         CountryInfo<Georgia>>>{}, "!");
 }

-- EDIT --

The OP ask

I was seeking for way to somehow iterate over countries and create objects. link line 5344.

It seem's to me that my solution make exactly this.

For your line 5344 case, I suppose you should apply my solution adding a delegating constructor; something as

template <std::size_t ... Is>
CountryInfoManager (std::index_sequence<Is...> const &)
  : m_countries{ new CountryInfo<static_cast<CountryName>(Is)>{}... }
 { }

CountryInfoManager ()
 : CountryInfoManager(
      std::make_index_sequence<static_cast<std::size_t>(
                CountryName::Size)>{})
 { }
like image 21
max66 Avatar answered Sep 28 '22 10:09

max66


You could use something like this:

template<std::size_t... I>
constexpr auto
countries(std::index_sequence<I...>)
{
    return std::make_tuple(CountryInfo<static_cast<CountryName>(I)>{}...);
}

constexpr auto
all_countries()
{
    return countries(std::make_index_sequence<Size>());
}

The result will be a tuple, with each index containing a country of corresponding type.

like image 21
eerorika Avatar answered Sep 28 '22 12:09

eerorika