C++ template partial specialization: Why cant I match the last type in variadic-template?
Asked Answered
P

4

10

I try to write a IsLast type traits to check if a given type is the last one in a std::tuple, but the code below does not compile. I know how to get around it but I am curious why the compiler does not like it.I guess there must be some rule on specialization of variadic-template that I am not aware of.

The code is at: https://godbolt.org/g/nXdodx

Error message:

error: implicit instantiation of undefined template
 'IsLast<std::tuple<std::__cxx11::basic_string<char>, int>, int>'

There is also a warning on specialization declaration:

warning: class template partial specialization contains template parameters that cannot be deduced; this partial specialization will never be used

#include <tuple>
#include <string>

/////////This works
template<typename TP, typename T>
struct IsFirst;

template<typename U, typename ...V, typename T>
struct IsFirst <std::tuple<U, V...>, T>
{
  enum {value = false};
};

template<typename U, typename ...V>
struct IsFirst <std::tuple<U, V...>, U>
{
  enum {value = true};
};

////////This doesn't compile
template<typename TP, typename T>
struct IsLast;

template<typename ...U, typename V, typename T>
struct IsLast <std::tuple<U..., V>, T>
{
  enum {value = false};
};

template<typename ...U, typename V>
struct IsLast <std::tuple<U..., V>, V>
{
  enum {value = true};
};


int main()
{
  using T = std::tuple<std::string, int>;
  bool v1 = IsFirst<T, std::string>::value;
  bool v2 = IsLast <T, int>::value;
}
Privily answered 4/2, 2017 at 16:28 Comment(5)
I believe that variadic template must come last.Europe
What compiler & version & compilation flags are you using? What is the exact error message?Colic
Do you mean move "typename ...U" to the last like below? It still does not compile. template<typename V, typename T, typename ...U,> struct IsLast <std::tuple<U..., V>, T> { enum {value = false}; };Privily
@BasileStarynkevitch: It is clang 3.9.1. godbolt.org/g/nXdodxPrivily
@xinwang: you should edit your question and also show the exact error message there.Colic
S
10

In a class template, the parameter pack must come after all other template parameters, so something like this is not allowed.

template<typename U, typename ...V, typename T>
struct IsFirst <std::tuple<U, V...>, T>
{
  enum {value = false};
};

In a function template, there can be other template parameters after the pack only if they can be deduced. This is not allowed for class templates because they do not allow deduction.

Scholastic answered 4/2, 2017 at 16:45 Comment(5)
IsFirst actually works. It is IsLast that does not compile.Privily
I see. I misread your answer. This makes sense. Thank you for the answer.Privily
You are wrong. This is only in the argument list that the parameter pack must be the last. Read my answer.Haemophilia
I could certainly believe that I'm wrong. I don't see your answer yet though.Scholastic
I have added it. The limitation of having template parameter in the last position only applies to template parameter pack expansion in argument list. For template class declaration, the argument list is the parameter list (this is literaly what is written in the standard §14.5.5), this is why the template parameter pack must be the last parameter of the class template declaration.Haemophilia
R
3

As observed by Resurrection, variadic template must be in last position.

But there are a lot of other ways to obtain the same result.

The obvious solution is to create a recursive type traits, but I show you a solution based on std::tuple_element

#include <tuple>
#include <iostream>
#include <type_traits>

template <typename C, typename T>
struct IsLast;

template <template <typename ...> class C, typename T, typename ... Ts>
struct IsLast<C<Ts...>, T>
 {
   using Tl
    = typename std::tuple_element<sizeof...(Ts)-1U, std::tuple<Ts...>>::type;

   static constexpr bool value { std::is_same<Tl, T>::value };
 };

int main ()
 {
   using T = std::tuple<std::string, int>;

   std::cout << IsLast<T, int>::value << std::endl;  // print 1
   std::cout << IsLast<T, long>::value << std::endl; // print 0
 }

Observe that this IsLast works with

 T = std::pairs<int, std::string>

and with other template classes.

Robbi answered 4/2, 2017 at 17:7 Comment(0)
H
2

EDIT: Thanks to the Bogdan comment, the standard citation is the correct one now!

According to temp.deduct.type#9:

If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context

Note that is only in the argument list of the template that the pack must be the last. Template parameter pack does not need to be the last in the template parameter list, this can be the case for template class partial specialization or template functions.

So your first exemple of class template partial specialization is correct:

template<typename U, typename ...V>
struct IsFirst <std::tuple<U, V...>, U>
{
   enum {value = true};
}

Because V... is the last argument of tuple.

template<typename ...V,typename U>
struct IsFirst <std::tuple<V..., U>, U>
{
   enum {value = true};
};

Because V... is not the last argument of tuple.

Haemophilia answered 5/2, 2017 at 9:46 Comment(1)
That paragraph applies to the template argument list of the partial specialization itself, not to nested template argument lists of the arguments of the specialization. What does apply in this case is [temp.deduct.type]/9: "If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context.". This is consistent with the compiler message mentioned in the question.Gunsel
V
0

There is a better solution:

#include <tuple>
#include <string>
#include <type_traits>

int main()
{
    using T = std::tuple<std::string, int>;

    constexpr size_t size = std::tuple_size<T>::value;

    typedef decltype(std::get<size - 1>(std::declval<T>())) LastType;

    static_assert(std::is_same<std::decay_t<LastType>, int>::value, "no");

}
Vaulting answered 4/2, 2017 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.