Есть ли какой-нибудь элегантный способ? (введите пакет параметров)

#c #templates #recursion #c 17 #variadic-templates

#c #шаблоны #рекурсия #c 17 #переменные-шаблоны

Вопрос:

Я хочу создать функцию, которая принимает типы и снова вызывать себя с помощью цикла или рекурсии.

То, что я уже пробовал, было приведено ниже, но оно вызывало только базовую функцию (Вызывало базовую функцию с AModule классом и не вызывало функцию с BModule.

 class AModule {
};
class BModule {
};

auto main() -> int {
  init<AModule, BModule>()
  return 0;
}

template<typename Module>
void init() {
  // BASE FUNCTION
  // Do something
}

template<typename... Modules>
void init() {
  (init<Modules>(), ...)
}

  

Комментарии:

1. Не могли бы вы пояснить, в чем проблема? Предоставляемый вами код вообще не должен компилироваться ( init ранее не объявлялся main ) — и даже если вы исправите это, добавив недостающие точки с запятой, я бы не ожидал описываемого вами поведения (вызывает, init<A> но не init<B> вызывает), я бы ожидал неоднозначного вызова init<B> .

Ответ №1:

В коде вашего примера вы используете сворачивание шаблонов, новую функцию C 17, которая позволяет вам избежать рекурсии.

Но, чтобы избежать столкновения имен, я предлагаю называть базовую версию по-другому; скажем do_something()

 template<typename Module>
void do_something() {
  // BASE FUNCTION
  // Do something
}

template<typename... Modules>
void init() {
  (do_something<Modules>(), ...);
}
  

Если вы действительно хотите использовать рекурсивный способ, вы можете сделать что-то следующим образом

 template <int = 0>
void init ()
 {
  // stop recursion; do nothing
 }

template <typename Head, typename ... Tail>
void init () 
 {
   // do something with Head

   // recursively call init()
   init<Tail...>();
 }
  

Хитрость в том, что вызов init<Tail...>(); , пока Tail... не станет пустым, называется Head / Tail... рекурсивной версией.

Когда Tail... значение пусто, init<Tail...>() это init<>() значит, что Head / Tail... version больше не соответствует ( Head ), но соответствует int = 0 версии; поэтому init<>() становится init<0>() , и регистр «ничего не делать» останавливает рекурсию.

Ответ №2:

В вашем коде (после исправления синтаксических ошибок) оба init() вызова были допустимы для 1 параметра шаблона, поэтому вызов был неоднозначным. Если сделать так, чтобы вызов с несколькими инициализациями требовал как минимум 2 параметра, эта двусмысленность устраняется.

 // bogus type that just prints an error message including the type it is parameterized with
template<typename T>
struct printer;

template<typename Module>
void init() {
 printer<Module>{}; // this is an error just to show you that it's getting called with both types
}

// only call this if it's called with 2 or more types, otherwise just use the other init()
template<typename T1, typename T2, typename... Modules>
void init() {
    init<T1>();
    init<T2>();
  (init<Modules>(), ...);
}

class AModule {
};
class BModule {
};

auto main() -> int {
  init<AModule, BModule>();
  return 0;
}
  

live:https://godbolt.org/z/D-eh2G