Although the idea of template metaprogramming - calculate something at compile time when possible - is wonderful,I wonder if current C 20 features allow us to avoid the TMP fully by using constexpr
, consteval
, if constexpr
,concept
, and other C 20 features. Is this true? Or some functionality that TMP offer is not able to be substituted?
CodePudding user response:
No, template metaprogramming cannot be fully replaced by C 20 language utilities; though a large amount can.
constexpr
, consteval
, etc. all certainly help lighten the load off of things that are traditionally done with TMP (as has been increasingly the case over the years), but templates still serve an orthogonal purpose of type-determination and, more importantly, type-generation.
constexpr
/consteval
/etc are limited in their expressiveness, since the parameters to such functions are not, themselves, constant expressions. This is true even in consteval
functions, despite the fact that this can only run at compile-time. This means that the following is not legal C :
template <typename T>
consteval auto make_array(std::size_t n) {
return std::array<T,n>{};
// ^ - error, 'n' is not a constant expression
}
This limitation means that more complex generation still requires conventional template-metaprogramming techniques. This especially becomes necessary when trying to produce variadic expansion.
(As stated in the comments, this would also prevent generating an appropriate type when parsing a string).
As an example, consider what the implementation of std::index_sequence
would look like without using intrinsics. The naive implementation of make_index_sequence<N>
which expands into index_sequence<0, 1, 2, ..., N-1>
, would look something like this:
template <std::size_t... Ints>
struct index_sequence
{
static constexpr std::size_t size() noexcept { return sizeof...(Ints); }
};
namespace detail {
template <bool End, std::size_t N, std::size_t...Tails>
struct make_index_sequence_impl
: make_index_sequence_impl<((N-1) == 0u), N-1, N-1, Tails...>{};
template <std::size_t N, std::size_t...Tails>
struct make_index_sequence_impl<true, N, Tails...>
: std::type_identity<index_sequence<Tails...>>{};
} // namespace detail
template <std::size_t N>
using make_index_sequence = typename detail::make_index_sequence_impl<(N==0u), N>::type;
Technically the index_sequence
example can be written in C 20 with recursive consteval
functions that return an instance of the index_sequence
-- but this only works because we can pass in a value to a template-deduced function. More complex type examples would not have this luxury at compile-time.
In general, more complex type-generation like this will require some level of TMP, especially if the type has restrictions on default-constructibility, or can't itself be a constant expression. In such a case, this would require partial template specializations which start to fall under general template-metaprogramming practices.