2

Here is a short program to print tuples using code adapted from answers from Johannes Schaub - litb and Luc Danton.

#include <iostream>
#include <tuple>

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};

template <int ...S, typename ...T>
void print(const std::tuple<T...> & tup, seq<S...> s) {
  int res[] = { (std::cout << std::get<S>(tup) << " ", 0)... };
  std::cout << std::endl;
}

int main() {
  std::tuple<double, int, char> tup(1.5, 100, 'c');
  print(tup, gens<std::tuple_size<decltype(tup)>::value >::type());
  return 0;
}

The second argument of print will always be always be gens<N>::type(), where N is the size of the tuple. I am trying to eschew the second argument to print by providing a default argument:

template <int ...S, typename ...T>
void print(const std::tuple<T...> & tup, seq<S...> s = gens<std::tuple_size<decltype(tup)>::value >::type()) {
  int res[] = { (std::cout << std::get<S>(tup) << " ", 0)... };
  std::cout << std::endl;
}

However, the result is a compiler error:

tmp5.cpp: In function ‘void print(const std::tuple<_Elements ...>&, seq) [with int ...S = {}; T = {double, int, char}]’:
tmp5.cpp:23:12: error: incomplete type ‘std::tuple_size&>’ used in nested name specifier

Do you know of any way to provide S... without the second argument to functions like print?

3 Answers 3

3

No, there is not.

As a matter of fact, this issue is not restricted to variadic template, it occurs for all template functions: the template type of an argument cannot be deduced from its default value.

template <typename T>
void func(T = 0) {} // expected-note {candidate template ignored:\
                                      couldn't infer template argument 'T'}

int main() {
  func(); // expected-error {no matching function for call to 'func'}
}

You need to switch gears.

The easiest way to do so would be to provide an overload that would be tasked with passing the second argument. After all, default arguments are just syntactic sugar to avoid writing a forwarding function.

Sign up to request clarification or add additional context in comments.

3 Comments

the clang style comments are really nerdy.
@JohannesSchaub-litb: I must admit I quite like this way of presenting compilation errors (when there is only a couple of them...)
Too bad. I'm working with heterogeneous tuples (tuple<array<double, N1>, array<double, N2>, ...>, the contents of which can all be sent to a template function template <int N> array<double, N> f(array<double, N>) to generate a new tuple. I guess for now, I'll set the sequence as a constant expression to avoid duplicate gens code in the second argument.
3

The problem is that the compiler has no way to deduce the index sequence S... if you do not provide the second function argument. By the time it gets to the default argument it needs to know what S... is, so it can't use the default argument to determine it.

This can be solved by providing an overload of print that builds the index list and forwards to the overload that accepts the index list:

template <typename ...T>
void print(const std::tuple<T...> & tup) {
  print(tup,typename gens<sizeof...(T)>::type());
}

2 Comments

I see what you mean. But is there any way to assign default arguments for variadic templates? I'm going to make several of these functions and want to make declaring and calling them as simple as possible...
There is no syntax for providing default arguments for a variadic parameter. The best you can do is provide a specialization or overload.
1

There might be a better way, but the easiest way I could think of was to add an extra level of indirection:

#include <iostream>
#include <tuple>

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};

template <typename ...T, int ...S>
void print_impl(const std::tuple<T...> & tup, seq<S...>) {
  int res[] = { (std::cout << std::get<S>(tup) << " ", 0)... };
  std::cout << std::endl;
}
// Pass args to real implementation here
template <typename ...T>
void print(const std::tuple<T...> & tup)
{
    print_impl(tup, typename gens<sizeof...(T)>::type());
}

int main() {
  std::tuple<double, int, char> tup(1.5, 100, 'c');
  print(tup);
  return 0;
}

1 Comment

I agree, however that is about as difficult as providing the sequence to print... I wonder if there is a way to provide default arguments to a variadic type?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.