Easter sale up to 25% off | 24 MAR — 3 APR

Compile-time arguments and function wrappers

24 August 2016

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:

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:

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:

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:

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 for cval_t<size_t, value>, there are aliases for int, bool and uint types.

Now we can write the following prototype for the swap function:

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:

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:

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:

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:

vec<T, 3> v = permute(pack(1, 2, 3), elements<2, 1, 0>);

and

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:

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.