Feb 8, 2015 - Accessing nth element of parameter pack

Since c++11 parameter pack was introduced. It can be used to store non-type template values, type templates, template templates or function arguments. In this post I will describe how to access parameter pack elements by index in function call.

First element

Accessing front is a simple query. In fact primitive implementation would not even require variadic template.

namespace fused {
  template <typename Tp>
  constexpr decltype(auto) front(Tp&& t, ...) noexcept;
}

First argument Tp&& t is of deduced type Tp&&. When Tp is a template parameter Tp&& it is not just rvalue reference, but forwarding reference that can bind to both prvalue/xvalue and lvalue references. Body of function fused::front is a one-liner just returning t by std::forward cast.

The key thing here is how to pass anything else beside t in a generic way. Passing via c-style ellipsis will swallow trailing arguments, but it does not work for uncopyable types like std::unique_ptr:

error: cannot pass objects of non-trivially-copyable type %T through ‘...’

Following implementation overcomes this problem with unnamed parameter pack Us&&...:

namespace fused {
  template <typename Tp, typename... Us>
  constexpr decltype(auto) front(Tp&& t, Us&&...) noexcept {
    return std::forward<Tp>(t);
  }
}

Us&&... expands to any number of forwarding references.

// test
// bind rvalue
auto x = fused::front(unsigned{1u}, 2.0, std::make_unique<char>('3'));
assert(x == 1u);

// bind lvalue
unsigned a = 4u;
auto const& y = fused::front(a, 2.0, std::make_unique<char>('3'));
assert(y == 4u);
assert(&a == &y);

second

Accessing second argument is not so trivial as fused::front. Ellipsis or parameter pack must appear last, so it cannot be used to swallow non-trailing arguments. To swallow single argument in front meta::ignore can be used:

namespace meta {
  struct ignore final {
    template <typename... Ts>
    constexpr ignore(Ts&&...) noexcept {}
  };
}

Job of meta::ignore is to literally do nothing. It has only a constructor, that allows to create meta::ignore instance of any arguments without modifying them. It is possible to implicitly convert any type to meta::ignore.

namespace fused {
  template <typename Tp>
  decltype(auto) second(meta::ignore, Tp&& t, ...) {
    return std::forward<Tp>(t);
  }
}
// test
// implicitly convert first argument to ignore
auto sec = fused::second(std::make_unique<unsigned>(1u),
  2.0, '3');
assert(sec == 2.0);

back

Accessing last argument of function call requires swallowing all preceding ones. N-argument function signature should consist of N-1 meta::ignores and single trailing argument that will be returned. Variadic template cannot be followed by a single parameter, fused::back uses a little trick to do so.

namespace fused {
  template <typename Tp, typename... Ts>
  constexpr decltype(auto) back(Tp&& t, Ts&&... ts) noexcept;
}

Function fused::back expects at least one argument and potentially zero-length tail. Additional consequence of above N-argument function signature split is that length of tail, sizeof...(Ts), is exactly equal to required N-1.

Wrapping meta::ignore in simple template alias allows to generate numerous implicit conversions regardless of used type.

namespace meta {
  template <typename>
  using eat = ignore;

  template <std::size_t>
  using eat_n = ignore;
}

Function fused::back internally contains a lambda that takes N-1 meta::eats and a trailing argument of auto&& type. Only last argument is returned.

namespace fused {
  template <typename Tp, typename... Us>
  constexpr decltype(auto) back(Tp&& t, Us&&... us) noexcept {
    return [](meta::eat<Us>..., auto&& x) -> decltype(x) {
      return std::forward<decltype(x)>(x);
    }(std::forward<Tp>(t), std::forward<Us>(us)...);
  }
}

Internal lambda is called in place by forwarding Tp&& t and all Us&&... us directly from fused::back. The trick here is to use fused::back arguments split to generate required number of meta::eats.

// test
auto a = 1u;
auto b = 2.0;
auto c = fused::back(1u, 2.0, '3');
assert(c == '3');

auto const& x = fused::back(a, b, c);
assert(x == '3');
assert(&c == &x);

auto y = fused::back(a, b, std::make_unique<char>('4'));
assert(y != nullptr);
assert(*y == '4');

nth

To access argument at any given index N of function call both techniques (skipping trailing and preceding) must be combined.

namespace detail {
  template<std::size_t... skip>
  struct at {
    template<typename Tp, typename... Us>
    constexpr decltype(auto) operator()(meta::eat_n<skip>...,
      Tp&& x, Us&&...) const noexcept {
      return std::forward<Tp>(x);
    }
  };
}

The problem is how to supply required number of values when instantiating detail::at template? It can be done with use of std::make_index_sequence, default template parameter and partial specialization.

namespace detail {
  template <std::size_t N, typename = std::make_index_sequence<N>>
  struct at;

  template <std::size_t N, std::size_t... skip>
  struct at<N, std::index_sequence<skip...>> {
    template <typename Tp, typename... Us>
    constexpr decltype(auto) operator()(meta::eat_n<skip>..., Tp&& x,
                                        Us&&...) const noexcept {
      return std::forward<Tp>(x);
    }
  };
}

Instantiating detail::at with single template parameter N will use default value for the second one, producing a list of integers in range [0, N). This list allows to expand meta::eat_n in function call signature for each index up to N-1. Next argument Tp&& x, at index N, will be returned. Following trailing arguments will be swallowed.

Generalized access of functiona argument parameter pack can be done with fused::nth.

namespace fused {
  template <std::size_t N, typename... Ts>
  decltype(auto) nth(Ts&&... args) {
    return detail::at<N>{}(std::forward<Ts>(args)...);
  }
}

Template parameters of fused::nth are partially deduced. Trailing variadic template typename... Ts can be deduced from given arguments Ts&&... args when fused::nth is called. Compiler cannot guess non-type template parameter std::size_t N, user has to pass it explicitly. std::make_unique or std::get work in similar way.

// test
auto a = 1u;
auto b = 2.0;
auto c = '3';

auto const& x = fused::nth<0>(a, b, c);
auto const& y = fused::nth<1>(a, b, c);
auto const& z = fused::nth<2>(a, b, c);

assert(&a == &x);
assert(&b == &y);
assert(&c == &z);

fused::nth<0>(a, b, c) = 7u;
assert(a == 7u);

Try this code!

References

Dec 10, 2014 - C++ type introspection at compile-time

Template metaprogramming

At first glance template metaprogramming may look very difficult, but in fact it is rather challenging than complicated. Sometimes TMP is abused to write unmaintainable code. On the other hand simple utilities can make your project much more flexible, generic and elegant. So what should we have in our toolbox?

apply

Let's start with something really simple: template metafunction apply that takes another metafunction and applies parameter pack to it.

namespace meta {
  template <template <typename...> class MetaFunction, typename... Args>
  struct apply {
    using type = typename MetaFunction<Args...>::type;
  };

  template <template <typename...> class MetaFunction, typename... Args>
  using apply_t = typename apply<MetaFunction, Args...>::type;
}

What is a template metafunction? It is simply a template that translates one or more parameters into types or values. What can be an argument of such metafunction? There are three possibilities:

Metafunction returns via named member types or values. According to common practice return member type should be named typename T::type or T::value if it is non-type. Since c++14 standard library has helper alias templates (see std::remove_reference_t) with _t suffix that allows to skip typename and ::type. In similar way variable templates can be used to skip ::value. There is no limitation for number of returns, but it is a good practice to have exactly one, still there are useful counterexamples like std::iterator_traits.

void_t

This year at cppcon Walter E. Brown had a great talk titled Modern Template Metaprogramming: A Compendium Part I & Part II. He introduced void_t - simple but powerfull metaprogramming tool (official proposal to the standard is N3911).

namespace meta {
  namespace detail {
    template <typename...>
    struct voider {
      using type = void;
    };
  }

  template <typename... Ts>
  using void_t = apply_t<detail::voider, Ts...>;
}

Usage of void_t does not directly rely on member alias using type = void. The trick is in template argument list. It can contain only well-formed types, otherwise entire void_t is ill-formed. This property can be detected with SFINAE

Type introspection

Type introspection is a possibility to query properties of a given type. In c++11 and c++14 it can be done at compile-time with type_traits or at runtime with RTTI. It is expected that in c++17 there will be more advanced facility called Concepts Lite.

has_member

void_t allows to write type trait for user-defined properties. Starting point is a struct has_member which exploits SFINAE.

namespace meta {
  template <template <typename> class, typename, typename = meta::void_t<>>
  struct has_member : std::false_type {};

  template <template <typename> class Member, typename Tp>
  struct has_member<Member, Tp, meta::void_t<Member<Tp>>> : std::true_type {};
}

has_member takes three parameters. First is template template metafunction that works like a getter. Second is a type to introspect. Third parameter (unnamed parameter with default value) is a technical trick that allows selecting implementation. There are two cases of has_member:

  • basic case, potentially handling any type, returns value == false,
  • refined case, handling only well-formed void_t<Member<Tp>>, returns value == true.

Refined case will be prefered as it is more specialized, so basic case will be instantiated only when void_t<Member<Tp>> is ill-formed.

ValueType

Following example presents how to write a trait that checks whether given type Tp has a member typename Tp::value_type.

template <typename Tp>
using ValueType = typename Tp::value_type;

template <typename Tp>
using has_value_type = meta::has_member<ValueType, Tp>;

Metafunction getter ValueType tries to extract value_type from Tp. If it will succeed void_t<Member<Tp>> is well-formed. Refined case of has_member is valid and its implementation is instantiated.

// test
static_assert(has_value_type<std::vector<int>>::value, "");
static_assert(!has_value_type<std::pair<int, int>>::value, "");
static_assert(!has_value_type<int>::value, "");

Public data members

Usage of has_member is not only limited to querying for member types. Next example shows how to write a trait that checks if a given type has accessible data member.

namespace detail {
  template <typename Tp>
  using InspectDataProperty = decltype(std::declval<Tp>().data);
}

template <typename Tp>
using has_data_property = meta::has_member<detail::InspectDataProperty, Tp>;

All the work is done by InspectDataProperty alias for decltype. Expression inside decltype parenthesis is in unevaluated context. This means that it is never executed. Compiler only simulates expression to obtain its type. To create an instance in unevaluated context simply constructor Tp{} can be called, but this works only when Tp is default constructible. More generic solution is to use std::declval instead.

Finally InspectDataProperty is equivalent to the type of the Tp member called data. When InspectDataProperty is able to obtain data then has_member template argument is well-formed and trait return value is true. Otherwise, when there is no data in a given type, has_member returns false.

// test
static_assert(has_data_property<TypeWithPublicData>::value, "");
static_assert(!has_data_property<std::vector<int>>::value, "");
static_assert(!has_data_property<TypeWithPrivateData>::value, "");
static_assert(!has_data_property<int>::value, "");

Member functions

Not only types or data members but also member functions can be detected by has_member. To check if any generic container supports memory reservation has_reserve trait can be used.

namespace detail {
  template <typename Tp>
  using InspectReserve = decltype(
      std::declval<Tp>().reserve(std::declval<typename Tp::size_type>()));
}

template <typename Tp>
using has_reserve = meta::has_member<detail::InspectReserve, Tp>;

InspectReserve helper simulates in decltype unevaluated context call of reserve member function. Similar to std::vector reserve should take size_type number of elements. Like in previous example, when helper is well-formed then has_reserve trait returns true.

// test
static_assert(has_reserve<std::vector<int>>::value, "");
static_assert(!has_reserve<std::array<int, 10>>::value, "");

Operators

Finally has_member can be used to the check presence of operators, since operators can be implemented as a member functions. This time unevaluated context in helper InspectCopyAssignable simulates assigning Tp const& to Tp&&.

namespace detail {
  template <typename Tp>
  using InspectCopyAssignable =
      decltype(std::declval<Tp>() = std::declval<Tp const&>());
}

template <typename Tp>
using is_copy_assignable = meta::has_member<detail::InspectCopyAssignable, Tp>;

It works for both copyable (returns true) and non-copyable types (return false);

// test
static_assert(is_copy_assignable<std::vector<int>>::value, "");
static_assert(is_copy_assignable<std::pair<int, int>>::value, "");
static_assert(!is_copy_assignable<std::unique_ptr<int>>::value, "");

But it fails for int.

// static_assert(is_copy_assignable<int>::value, ""); //fails

I leave it to the readers as a puzzle how to fix InspectCopyAssignable so that it work also for fundamental types. Here is a small hint... take a look at reference collapsing and this build error message:

error: using xvalue (rvalue reference) as lvalue
using int_t = decltype(std::declval<int>() = std::declval<int const&>());
                                           ^

Try this code.

References