Как улучшить глубину рекурсии шаблона, необходимую для этого шаблона?

я использую шаблон, описанный в этом вопросе SO в моем коде, чтобы сделать списки регистрации во время компиляции различных видов:Регистрация типа C++ во время компиляции trick

например, если у вас есть куча функций обратного вызова lua, и вы не хотите забывать регистрировать их в некотором состоянии lua, вы можете объявить их с помощью макроса, который помещает тип шаблона, который знает их имя и указатель функции в список, а затем у вас есть один-лайнер, который регистрирует все функции.

ограничение этой техники (как описано в ответе SO) заключается в том, что если у вас есть n элементы в списке, это требует глубины рекурсии шаблона O(n) для оценки. Это не идеально, у меня на самом деле уже есть несколько функций обратного вызова lua...

я верил, что O(n) глубина рекурсии неизбежна по разным причинам, однако, как я недавно узнал от Yakk в этом (unreleated) ответе, некоторые фундаментальные вещи, которые, как я наивно полагал, требовались O(n) на самом деле можно сделать в O(log n) глубину. переключатель заявление variadic шаблон расширения

в частности, больше нет причин, по которым задействованные структуры данных должны требовать O(log n) глубина шаблона для их операций.

часть, в которой я не уверен, это Rank трюк. Из ссылочного кода этот шаблон

template <int N>
struct Rank : Rank<N - 1> {};

template <>
struct Rank<0> {};

является ключевым ингредиентом. Это дорого в условия глубины шаблона, хотя -- instantiating Rank<N> требуется глубина шаблона N. Важным свойством, которое он имеет, является то, что если функция f определяется, который перегружен с использованием многих различных типов ранга, разрешение перегрузки всегда выбирает перегрузку наибольшего ранга, потому что это "самый производный экземпляр". Е. Г. если у нас есть этот код:

bool f(Rank<0>) { return false; }
int f(Rank<1>) { return 0; }
float f(Rank<2>) { return 0; }
double f(Rank<3>) { return 0; }

тогда это всегда так, что в любой момент кода, decltype(f(Rank<100>{})) имеет тип, равный возвращение значение последней определенной перегрузки. Т. е. эти утверждения проходят

bool f(Rank<0>) { return false; }
static_assert(std::is_same<bool, decltype(f(Rank<100>{}))>::value, "D:");
int f(Rank<1>) { return 0; }
static_assert(std::is_same<int, decltype(f(Rank<100>{}))>::value, "D:");
float f(Rank<2>) { return 0; }
static_assert(std::is_same<float, decltype(f(Rank<100>{}))>::value, "D:");
double f(Rank<3>) { return 0; }
static_assert(std::is_same<double, decltype(f(Rank<100>{}))>::value, "D:");

есть ли способ сделать это, который не требует глубины рекурсии шаблона O(n)?

возможно, используя более тщательно выбранные параметры для перегрузки функции (?)

1 ответов


чтобы избежать ошибки типа:

глубина создания экземпляра шаблона превышает максимум 900 (используйте-ftemplate-depth= для увеличения максимума) создание экземпляра 'struct Rank'

не увеличивая глобально максимальную глубину шаблона, вы можете создавать промежуточные шаблоны:

// To allow instantiation of Rank<1000> with template-depth at 256
template struct Rank<250>;
template struct Rank<500>;
template struct Rank<750>;
template struct Rank<1000>;

у вас также может быть помощник:

namespace detail
{

    template <template <std::size_t> class C,
              typename Seq,
              std::size_t BlockSize>
    struct Instantiate_Impl;

    template <template <std::size_t> class C,
              std::size_t... Is,
              std::size_t BlockSize>
    struct Instantiate_Impl<C, std::index_sequence<Is...>, BlockSize>
    {
        std::tuple<C<(Is * BlockSize)>...> dummy;
    };
}

template <template <std::size_t> class C,
          std::size_t N,
          std::size_t BlockSize = 250>
struct Instantiate :
    detail::Instantiate_Impl<C,
                             std::make_index_sequence<1 + N / BlockSize>,
                             BlockSize>
{};

а то

template struct Instantiate<Rank, 2000, 250>; // Rank<2000> is now instantiated.