Passing values in C++ as a template arguments has some inflexibilities.
For example, if template function has been wrapped into a functor class,
there are no ways to pass some value as a template argument.
There is the following template function:
1 2 3 |
template <size_t count, typename T, size_t N> vec<T, N> swap(const vec<T, N>& x); |
which requires first template argument to be passed at every call,
but we can’t wrap it to the functor:
1 2 3 4 5 6 7 8 9 |
struct fn_swap { template <typename... Args> auto operator()(Args&&... args) { return swap< ??? >(std::forward<Args>(args)...); } }; |
As a first attempt, we can move template parameters to the wrapper itself:
1 2 3 4 5 6 7 8 9 10 |
template <size_t count> struct fn_swap { template <typename... Args> auto operator()(Args&&... args) { return swap<count>(std::forward<Args>(args)...); } }; |
Good enough until we want to create this wrapper in one place and pass count
in another.
Solution
All these can be easily worked around by introducing a new type cval_t
which can hold compile-time value and can be passed as a regular argument.
This is small fragment of cval_t
implementation from the CoMeta C++14 metaprogramming library:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
template <typename T, T val> struct cval_t { constexpr static T value = val; constexpr cval_t() noexcept = default; constexpr cval_t(const cval_t&) noexcept = default; constexpr cval_t(cval_t&&) noexcept = default; using value_type = T; using type = cval_t; constexpr operator value_type() const { return value; } constexpr value_type operator()() const { return value; } }; template <size_t val> using csize_t = cval_t<size_t, val>; template <size_t val> constexpr csize_t<val> csize{}; |
In addition to
csize_t
, which is an alias forcval_t<size_t, value>
, there are aliases forint
,bool
anduint
types.
Now we can write the following prototype for the swap
function:
1 2 3 |
template <size_t count, typename T, size_t N> vec<T, N> swap(csize_t<count>, const vec<T, N>& x); |
and pass all arguments including those that must be known at compile-time to our wrapper:
1 2 3 4 |
fn_swap swap; // we don't have to specify count here vec<T, 4> x; x = swap(csize<2>, x); // pass number as a regular argument |
What about a list of values?
For passing a list of compile-time values to arbitrary functions we can create
a class similar to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
template <typename T, T... values> struct cvals_t { using type = cvals_t<T, values...>; constexpr static size_t size() { return sizeof...(values); } template <size_t index> constexpr T operator[](csize_t<index>) { return get(csize<index>); } template <size_t index> constexpr static T get(csize_t<index> = csize_t<index>()); constexpr static T front() { return get(csize<0>); } constexpr static T back() { return get(csize<size() - 1>); } // to be able to iterate this list in for(T v: vals) static const T* begin() { return array(); } static const T* end() { return array() + size(); } }; |
With this class, passing numeric and boolean constants to various functions is quite easy:
1 2 3 4 5 6 |
template <size_t... Indices, typename T, size_t N> inline vec<T, N> permute(const vec<T, N>& x, elements_t<Indices...> = elements_t<Indices...>()) { return shufflevector<N, internal::shuffle_index_permute<N, Indices...>>(x); } |
Note, that both ways of passing arguments are perfectly possible:
1 2 |
vec<T, 3> v = permute(pack(1, 2, 3), elements<2, 1, 0>); |
and
1 2 |
vec<T, 3> v = permute<2, 1, 0>(pack(1, 2, 3)); |
And we don’t have to write two different prototypes to make both ways work.
Compile-time math
In CoMeta there are operator overloads defined for cval_t
and cvals_t
types
and all their specializations.
This means that all regular calculations
which can be applied to number constants, can be applied to compile-time values too.
Few examples:
1 2 3 4 5 6 |
constexpr auto x = csizes<10, 20, 30, 40, 50>; constexpr auto a = x[csize<1>]; // get second value, a = csize<20> constexpr auto b = x[csizes<0, 1>]; // get first two values constexpr auto c = x[csizes<4, 3, 2, 1, 0>]; // reverse x constexpr auto d = x / csize<10>; // d = csizes<1, 2, 3, 4, 5> |
All these techniques are widely used in the KFR C++ DSP framework for template expressions.