Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial Specialization of tuple contents with variadic arguments

Currently, I'm trying to get some code to react differently to different types. This isn't the exact code, but it gets the message across.

template<class A, class B>
struct alpha {
  enum { value = 0 };
};

template<class T, class... Args>
struct alpha<std::tuple<Args...>, T> {
  enum { value = 1 };
};

// This gets ignored
template<class T, class... Args>
struct alpha<std::tuple<Args..., std::vector<T> >, T> {
  enum { value = 2 };
};

// This gets ignored
template<class T, class... Args>
struct alpha<std::tuple<Args..., T>, T> {
  enum { value = 3 };
};

template<class T, class... Args>
struct alpha<T, std::tuple<Args...> > {
  enum { value = 4 };
};

template<class... LArgs, class... RArgs>
struct alpha<std::tuple<LArgs...>, std::tuple<RArgs...> > {
  enum { value = 5 };
};

int main(int argc, char* argv[]) {
  std::cout << alpha<std::tuple<int, double>, double>::value << std::endl; // prints 1
  return 0;
}

I've tried more than this code shows, but nothing works so far and I ran across a problem with explicit specialization in a non-namespace scope. For reference, I'm working on gcc 4.6 (the one that comes with oneiric server), which I believe has complete variadic template support. I don't care how ugly it gets if the implementation works to detect the last argument of the parameter pack and the other types as well. Any suggestions?

EDIT: I wanted to share the solution I used based on the answers (this is an example).

template<typename T> struct tuple_last;

template<typename T, typename U, typename... Args>
struct tuple_last<std::tuple<T,U,Args...>> {
  typedef typename tuple_last<std::tuple<U,Args...>>::type type;
};

template<typename T>
struct tuple_last<std::tuple<T>> {
  typedef T type;
};

namespace details {
// default case:
template<class T, class U>
struct alpha_impl {
enum { value = 1 };
};

template<class T>
struct alpha_impl<T, T> {
enum { value = 101 };
};

template<class T>
struct alpha_impl<T, std::vector<T>> {
enum { value = 102 };
};

// and so on.
}

template<class T, class... Args>
struct alpha<std::tuple<Args...>, T>
  : details::alpha_impl<T, tuple_last<std::tuple<Args...>>;
like image 567
norcalli Avatar asked Jul 20 '11 06:07

norcalli


3 Answers

If you compile using clang, it helpfully reports that (2) and (3) are unusable. The warning for (3), which you expect to be selected, is as follows:

warning: class template partial specialization contains a template parameter that can not be deduced; this partial specialization will never be used

struct alpha<std::tuple<Args..., T>, T> {
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

note: non-deducible template parameter 'Args'

template<class T, class... Args>
                           ^

Why is Args not deducible? The C++0x FDIS states at §14.8.2.5/9:

If the template argument list of [a type that is specified in terms of template parameters] contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context.

In your specialization, the type std::tuple<Args..., T> is a type that is specified in terms of template parameters Args and T. It contains a pack expansion (Args...), but that pack expansion is not the last template argument (T is the last template argument). Thus, the entire template argument list of the tuple (the entirety of <Args..., T>) is a non-deduced context.

The argument list of the std::tuple is the only place in the template specialization's argument list that Args appears; since it is not deducible from there, it is not deducible at all and the specialization will never be used.

Matthieu M. provides a clever workaround in his answer.

like image 179
James McNellis Avatar answered Oct 14 '22 04:10

James McNellis


@James provided the why, now let's try to find an alternative.

I would suggest using another level of indirection.

1. Getting the last argument

template <typename T> struct Last;

template <typename T, typename U, typename... Args>
struct Last<std::tuple<T,U,Args...>>
{
  typedef typename Last<std::tuple<U,Args...>>::type type;
};

template <typename T>
struct Last<std::tuple<T>>
{
  typedef T type;
};

2. Introducing a specialized helper

template <typename T, typename U>
struct alpha_tuple
{
  enum { value = 1 };
};

template <typename T>
struct alpha_tuple<T,T>
{
  enum { value = 3 };
};

template <typename T>
struct alpha_tuple<std::vector<T>,T>
{
  enum { value = 2; }
};

3. Hooking it up

template <typename T>
struct alpha<std::tuple<>, T>
{
  enum { value = 1 };
};

template <typename T, typename U, typename Args...>
struct alpha<std::tuple<U, Args...>, T>
{
  typedef typename Last<std::tuple<U, Args...>>::type LastType;
  enum { value = alpha_tuple<LastType,T>::value };
};

Note that there is no last type for empty tuples, so I had to deal with them in a separate specialization.

like image 40
Matthieu M. Avatar answered Oct 14 '22 03:10

Matthieu M.


If you like to find out whether a tuple as a specific last member, here's a type trait for that:

#include <type_traits>
#include <tuple>

template <typename ...Args> struct back;
template <typename T, typename ...Args> struct back<T, Args...>
  { typedef typename back<Args...>::type type; };
template <typename T> struct back<T>
  { typedef T type; };


template <typename...> struct tuple_has_last : public std::false_type {};
template <typename T, typename... Args> struct tuple_has_last<T, std::tuple<Args...>>
{
  static const bool value = std::is_same<typename back<Args...>::type, T>::value;
};

Edit: Oh, I didn't see that Matthieu had already written the exact same thing. Never mind.

like image 24
Kerrek SB Avatar answered Oct 14 '22 03:10

Kerrek SB