«Строитель» для шаблона функции - PullRequest
0 голосов
/ 26 мая 2018

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

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

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

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

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

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

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

1 Ответ

0 голосов
/ 26 мая 2018

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

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

...