Построитель шаблона функции

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

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

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

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

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

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

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


person BeeOnRope    schedule 26.05.2018    source источник
comment
Возможный дубликат Named? параметры в шаблонах, функциях   -  person xskxzr    schedule 26.05.2018
comment
Половина этого вопроса более или менее дублирует этот. К сожалению, другая часть вопроса касается именованных параметров функции, и принятый ответ отвечает только на эту часть (это риск объединения двух вопросов в один). Поэтому я думаю, что вопрос конкретно о аргументе шаблона имеет смысл. Учтите, например, что пока что один ответ, который, кажется, работает довольно хорошо, не упоминается в другом вопросе.   -  person BeeOnRope    schedule 26.05.2018


Ответы (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 доступными с некоторыми более описательными именами или в некоторых соответствующих именах. пространство имен.

person aschepler    schedule 26.05.2018