Reputation: 1756
I'm studying metaprogramming in C++ with templates and I'm trying to implement a typelist, and operations on it.
I'm defining a typelist as a variadic class template and operations as templated structs with partial specializations. Operations like Front, PopFront and PushFront work fine, but when I instantiate Back and Element (to index the typelist to get the n-th element), the compiler complains that I'm using an incomplete type:
/**** typelist ****/
template <typename... Types>
struct Typelist
{
};
/**** get first element ****/
template <typename List>
struct Front;
template <typename Head, typename... Tail>
struct Front<Typelist<Head,Tail...>>
{
typedef Head type;
};
template <typename List>
using FrontT = typename Front<List>::type;
/**** pop first element ***/
template <typename List>
struct PopFront;
template <typename Head, typename... Tail>
struct PopFront<Typelist<Head,Tail...>>
{
using type = Typelist<Tail...>;
};
template <typename List>
using PopFrontT = typename PopFront<List>::type;
/**** push first element ****/
template <typename List, typename Element>
struct PushFront;
template <typename... Elements, typename Element>
struct PushFront<Typelist<Elements...>,Element>
{
using type = Typelist<Element,Elements...>;
};
template <typename List, typename Element>
using PushFrontT = typename PushFront<List,Element>::type;
/**** get last element ****/
template <typename List>
struct Back;
template <typename... Head, typename Tail>
struct Back<Typelist<Head...,Tail>>
{
typedef Tail type;
};
/**** indexing ****/
template <typename List, unsigned Index> // recursive case
struct Element
{
using type = typename Element<typename PopFront<List>::type, Index - 1>::type;
};
template <typename List>
struct Element<List,0> // base case
{
typedef typename Front<List>::type type;
};
// template <typename... Types> // base case
// struct Element<Typelist<Types...>,0>
// {
// using type = typename Front<Typelist<Types...>>::type;
// };
// template <typename... Types, unsigned Index> // recursive case
// struct Element<Typelist<Types...>,Index>
// {
// using type = typename Element<typename PopFront<Typelist<Types...>>::type, Index - 1>::type;
// };
template <typename List, unsigned Index>
struct ElementI : ElementI<PopFrontT<List>,Index - 1>
{
};
template <typename List>
struct ElementI<List,0> : Front<List>
{
};
template <typename List, unsigned Index>
using ElementT = typename Element<List,Index>::type;
I understand that I can use template parameter packs for any parameter in partial specializations as long as arguments can be deduced, so I think the declaration is correct, right?
EDIT Element now works, I made a spelling mistake calling it, Back still doesn't, and I can't understand why.
EDIT this is the code to test the typelist and the compiler (GCC 7.2) error (I slightly changed the implementation of typelist):
EDIT EDIT compiler is GCC 7.2
#include "typelist.hpp"
#include <type_traits>
int main(int argc, char **argv)
{
Typelist<int, double, bool> tl;
static_assert(std::is_same<typename Front<decltype(tl)>::type,int>::value, "not same");
static_assert(std::is_same<typename PopFront<decltype(tl)>::type,Typelist<double,bool>>::value, "not same");
static_assert(std::is_same<typename PushFront<decltype(tl),float>::type,Typelist<float,int,double,bool>>::value, "not same");
/* compiler error */ static_assert(std::is_same<typename Back<decltype(tl)>::type,bool>::value, "not same");
static_assert(std::is_same<typename ElementI<decltype(tl),0>::type,int>::value, "not same");
static_assert(std::is_same<typename ElementI<decltype(tl),1>::type,double>::value, "not same");
static_assert(std::is_same<ElementT<decltype(tl),2>,bool>::value, "not same");
return 0;
}
main.cpp: In function 'int main(int, char**)':
main.cpp:11:61: error: invalid use of incomplete type 'struct Back<Typelist<int, double, bool> >'
static_assert(std::is_same<typename Back<decltype(tl)>::type,bool>::value, "not same");
^~~~
In file included from main.cpp:1:0:
typelist.hpp:48:8: note: declaration of 'struct Back<Typelist<int, double, bool> >'
struct Back;
^~~~
main.cpp:11:70: error: template argument 1 is invalid
static_assert(std::is_same<typename Back<decltype(tl)>::type,bool>::value, "not same");
Upvotes: 2
Views: 6280
Reputation: 264331
With clang++
(Apple clang version 11.0.3 (clang-1103.0.32.59)) I get the following message:
t.cpp:51:8: error: class template partial specialization contains template parameters that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct Back<Typelist<Head...,Tail>>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.cpp:50:23: note: non-deducible template parameter 'Head'
template <typename... Head, typename Tail>
^
t.cpp:50:38: note: non-deducible template parameter 'Tail'
template <typename... Head, typename Tail>
^
From: https://en.cppreference.com/w/cpp/language/parameter_pack (sorry i don't have a primary ref)
In a primary class template, the template parameter pack must be the final parameter in the template parameter list.
In a function template, the template parameter pack may appear earlier in the list provided that all following parameters can be deduced from the function arguments, or have default arguments:
template<typename... Ts, typename U> struct Invalid; // Error: Ts.. not at the end
template<typename ...Ts, typename U, typename=void>
void valid(U, Ts...); // OK: can deduce U
// void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position
valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int,int,int}
But we can solve the issue by defining Back
in terms of Element
.
/**** get last element ****/
template <typename List>
struct Back;
template <typename... Args>
struct Back<Typelist<Args...>>
{
using type = typename Element<Typelist<Args...>, sizeof...(Args) - 1>::type;
};
Question:
Why do you have template using
statements for all your types except Back
and Element
?
Upvotes: 1