4

I'm aware that sizeof...(Args...) yields the number of types in a C++0x packed template argument list, but I wanted to implement it in terms of other features for demonstation purposes, but it won't compile.

// This is not a solution -- overload ambiguity.
// template <typename... Args> size_t num_args ();          // Line 7
// template <>
constexpr size_t num_args ()
{
    return 0;
}

template <typename H, typename... T>
constexpr size_t num_args ()                                // Line 16
{
    return 1 + num_args <T...> (); // *HERE*
}

int main ()
{
    std :: cout << num_args <int, int, int> ();
}

This errors at *HERE* with

No matching function call to ...
... candidate is template<class H, class ... T> size_t num_args()

i.e. it's not seeing the base case which is defined first. Forward-declaring template<typename...T>num_args(); introduces ambiguity in overload resolution.

x.cpp:30:45: note: candidates are:
x.cpp:7:36: note: size_t num_args() [with Args = {int, float, char}, size_t = long unsigned int]
x.cpp:16:9: note: size_t num_args() [with H = int, T = {float, char}, size_t = long unsigned int]

I am using gcc 4.6. How can I make this work?

Thanks.

spraff
  • 32,570
  • 22
  • 121
  • 229

2 Answers2

8

You didn’t declare a base case. You have a template-free overload of your num_args function but when calling a function num_args<T...>() this will never be found, for obvious reasons: it will always try to instantiate a function template.

You can however specialise your function template to perform the desired operation.

template <>
constexpr size_t num_args<>()
{
    return 0;
}

However, this won’t work either since here you’re specialising a parameterless function template and such a template doesn’t exist: your other function template num_args always has at least one argument, H.

In order to really make this work you need partial specialisations, and these only exist for class templates. So this is what you need here.

template <typename T>
struct num_args_t;

template <>
struct num_args_t {
    static size_t const value = 0;
};

template <typename H, typename T...>
struct num_args_t {
    static size_t const value = num_args_t<T...>::value + 1;
};

template <typename T...>
constexpr size_t num_args() {
    return num_args_t<T...>::value;
}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • Thanks but I dont' get why it matters that the "other function template always has at least one argument, H" -- imagine uncommenting out the first two lines of my post -- when recursing into `num_args()` with empty `Args...` surely that should match the base case?!?!? How else can the "safe printf" example work? – spraff Aug 18 '11 at 13:41
  • @spraff Again: you don’t *have* a base case. There doesn’t exist a function `num_args` which could have such a base case. You do have `num_args` but this is a whole different function: it has at least one argument, so a function template with zero arguments is *not* a specialisation of it. – Konrad Rudolph Aug 18 '11 at 13:44
  • @spraff: Because you can't have partial specializations of functions. – R. Martinho Fernandes Aug 18 '11 at 13:46
  • I can't get this to work for the same reason: http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html -- is it out-of-date? – spraff Aug 18 '11 at 13:52
  • I DO have a base case template which is commented-out because it triggers a different error. Please see my edits to the original post. Also, this function is not partially-specialised, all the arguments remain templates -- `num_args` would be a partial specialisation – spraff Aug 18 '11 at 13:57
  • @spraff You mean you have an unspecialised template (not a base case). This won’t compile because you cannot partially specialise function templates (`num_args` would be a partial specialisation), as I’ve mentioned in my answer. – Konrad Rudolph Aug 18 '11 at 14:07
  • What's the difference between `template<> size_t num_args<> ();` and a base case? And what's the difference between `template<> size_t num_args<> ();` and `size_t num_args();`? Per the latter, I've called non-template functions from template functions in C++98 without any problem. – spraff Aug 18 '11 at 14:20
  • @spraff The first is a base case. That’s undisputed. However, this only works if there is a general template that this can be a specialisation of, which isn’t the case in your code. As for the difference between a specialisation and an overload, consult the GOTW item or the recent SO question: http://stackoverflow.com/q/7108033/1968 – Konrad Rudolph Aug 18 '11 at 16:51
4

Konrad's answer should get you going, but I think the more idiomatic way such things are usually expressed is with static member constants, so I just wanted to present that solution:

#include <type_traits>

template <typename...> struct arg_size;  // no primary definition needed

template <typename T, typename ...Args> struct arg_size<T, Args...>
  : public std::integral_constant<std::size_t, 1 + arg_size<Args...>::value> { };

template <> struct arg_size<>
  : public std::integral_constant<std::size_t, 0> { };

Then you get your argument pack size via arg_size<Args...>::value.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084