You could use a set of tag classes to provide names for each compile-time parameter and allow users to supply just the parameters they want to, and in any order:
// 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>>();
}
Note everything in FooArgs::detail
is pretty generic, so if you wanted to use this pattern with more than one set of tags, you could move all that to some other header file and make get_type
and get_value
available with some more descriptive names or in some appropriately named namespace.