«Строитель» для шаблона функции

Рассмотрим следующую функцию-шаблон:

template<typename T1 = Type1, typename T2 = Type2, int X1 = DefaultX1, /* ...and so on */>
int foo(/* skipped */) { ... }

Ключ в том, что он имеет длинный список параметров шаблона, как типовых, так и нетиповых, со значениями по умолчанию.

Большинство пользователей захотят вызывать эту функцию только с нулем, одним или небольшим количеством параметров шаблона, замененных по умолчанию. Однако это удобно с существующим определением только в том случае, если первым параметром, подлежащим переопределению, является T1 - в любом другом случае они должны явно перечислить все предыдущие параметры и их значения по умолчанию. Например, чтобы переопределить X1:

int x = foo<Type1,Type2,42>(...);

Мне нужен шаблон, который позволяет пользователям переопределять ноль или более аргументов шаблона и вызывать функцию без упоминания аргументов, которые они не переопределяют.

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

Возможный дубликат По имени? параметры в шаблонах, функции

xskxzr 26.05.2018 08:13

Половина этого вопроса более или менее повторяет этот. К сожалению, другая часть вопроса касается «параметров именованных функций», и принятый ответ отвечает только на эту часть (это риск объединения двух вопросов в один). Поэтому я думаю, что вопрос, касающийся аргументов в пользу шаблонов, имеет смысл. Рассмотрим, например, что один ответ, который, кажется, работает довольно хорошо, не упоминается в другом вопросе.

BeeOnRope 26.05.2018 08:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
58
1

Ответы 1

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

// In your header file:
#include <type_traits>

namespace FooArgs {
    template <typename T> struct T1 {};
    template <typename T> struct T2 {};
    template <int N> struct X1 {};
    // ...

    namespace detail {
        template <typename Enable, class Matcher, unsigned int N, class... Tags>
        struct match_at_most_enable;
        template <class Matcher, unsigned int N>
        struct match_at_most_enable<void, Matcher, N>
            : std::true_type {};
        template <class Matcher, unsigned int N, class Tag1, class... Tags>
        struct match_at_most_enable<
            typename std::enable_if<!Matcher::template match<Tag1>::value>::type,
            Matcher, N, Tag1, Tags...>
            : match_at_most_enable<void, Matcher, N, Tags...>::type {};
        template <class Matcher, unsigned int N, class Tag1, class... Tags>
        struct match_at_most_enable<
            typename std::enable_if<Matcher::template match<Tag1>::value>::type,
            Matcher, N, Tag1, Tags...>
            : match_at_most_enable<void, Matcher, N-1, Tags...>::type {};
        template <class Matcher, class Tag1, class... Tags>
        struct match_at_most_enable<
            typename std::enable_if<Matcher::template match<Tag1>::value>::type,
            Matcher, 0, Tag1, Tags...>
            : std::false_type {};
        template <class Matcher, unsigned int N, class... Tags>
        using match_at_most = match_at_most_enable<void, Matcher, N, Tags...>;

        template <class... Tags> struct inherit_all : Tags... {};
        template <class... Tags>
        constexpr inherit_all<Tags...>* combine() { return nullptr; }

        template <template<typename> class TT, typename DfltType, class... Tags>
        struct get_type_helper {
            template <class Tag>
            struct match : std::false_type {};
            template <typename T>
            struct match<TT<T>> : std::true_type {};
            static_assert(match_at_most<get_type_helper, 1, Tags...>::value,
                "An argument tag was specified more than once");

            template <typename T>
            struct wrap { using type = T; };
            template <typename T>
            static wrap<T> select(TT<T>*);
            static wrap<DfltType> select(...);
            using type = typename decltype(select(combine<Tags...>()))::type;
        };
        template <template<typename> class TT, typename DfltType, class... Tags>
        using get_type = typename get_type_helper<TT, DfltType, Tags...>::type;

        template <typename T, template<T> class TT, T DfltValue, class... Tags>
        struct get_value_helper {
            template <class Tag>
            struct match : public std::false_type {};
            template <T Value>
            struct match<TT<Value>> : public std::true_type {};
            static_assert(match_at_most<get_value_helper, 1, Tags...>::value,
                "An argument tag was specified more than once");

            template <T Value>
            static constexpr T select(TT<Value>*) { return Value; }
            static constexpr T select(...) { return DfltValue; }
            static constexpr T value = select(combine<Tags...>());
        };
        // Note if using C++17 or later, get_value could be a
        // constexpr variable template instead of a function.
        template <typename T, template<T> class TT, T DfltValue, class... Tags>
        constexpr T get_value()
        { return get_value_helper<T, TT, DfltValue, Tags...>::value; }
    }
}

template <class... Tags>
int foo(/*params*/)
{
    using namespace FooArgs::detail;
    using T1 = get_type<FooArgs::T1, Type1, Tags...>;
    using T2 = get_type<FooArgs::T2, Type2, Tags...>;
    constexpr int X1 = get_value<int, FooArgs::X1, DefaultX1, Tags...>();
    // ...
}

// Example usage:
void bar() {
    int n = foo<FooArgs::X1<42>, FooArgs::T1<int>>();
}

Обратите внимание, что все в FooArgs::detail является довольно общим, поэтому, если вы хотите использовать этот шаблон с более чем одним набором тегов, вы можете переместить все это в какой-либо другой файл заголовка и сделать get_type и get_value доступными с некоторыми более описательными именами или в некоторых с соответствующими именами. пространство имен.

Другие вопросы по теме