I'm trying to use recursion with variadic templates. I would like the base case to have zero template arguments. After looking through stackoverflow answers to previous questions, I have found two kinds of responses to this problem:
- You should not specialize templates functions. Herb Sutter wrote about that here: http://www.gotw.ca/publications/mill17.htm
- You use
template <typename = void>ortemplate <typename T = void>. For example, the first answer here: How to write a variadic template recursive function?
I attempted to use the solution (2) in my problem, but received errors. This is a Minimal, Reproducible Example:
#include <iostream>
template<typename = void> // base case
int NumArguments() {
return 0;
}
template<typename FirstArg, typename... RemainingArgs>
int NumArguments() {
return 1 + NumArguments<RemainingArgs...>();
}
class A {
public:
A() {}
};
int main() {
std::cout << NumArguments<A>();
return 0;
}
Compilation in Microsoft Visual C++20 gave the errors:
error C2668: 'NumArguments': ambiguous call to overloaded function
message : could be 'int NumArguments<A,>(void)'
message : or 'int NumArguments<A>(void)'
message : while trying to match the argument list '()'
What does this error message mean? How do I create a zero-argument base case for recursion with variadic templates?
Edit: There were requests in the comments for a more complete description of my problem. The question really is the question title, and not "how do I get my code to work?", but I have not yet gotten my code to compile, so I have decided to share it.
NumArguments is a stand-in for another function ComputeSize that takes as input Args and returns an std::size_t.
template<typename = void>
constexpr std::size_t ComputeSize() {
return 0;
}
template<typename FirstArg, typename... RemainingArgs>
constexpr std::size_t ComputeSize() {
return FuncReturnSize<FirstArg>() + ComputeSize<RemainingArgs...>();
}
The possible list of Args in Args is finite and known prior to compilation. FuncReturnSize is overloaded for each of these Args. For example, two possible "overloads"(?) are
template <typename T>
requires ((requires (T t) { { t.Func()} -> std::same_as<double>; }) || (requires (T t) { { t.Func() } -> std::same_as<std::vector<double>>; }))
constexpr std::size_t FuncReturnSize() {
return 1;
}
template <typename T>
requires requires (T t) { { t.Func() } -> is_std_array_concept<>; }
constexpr std::size_t FuncReturnSize() {
return std::tuple_size_v<decltype(std::declval<T&>().Func())>;
}
The concept is_std_array_concept<> should check if the return value of t.Func() is some size array. I am not yet sure if it works. It is defined by
template<class T>
struct is_std_array : std::false_type {};
template<class T, std::size_t N>
struct is_std_array<std::array<T, N>> : std::true_type {};
template<class T>
struct is_std_array<T const> : is_std_array<T> {};
template<class T>
struct is_std_array<T volatile> : is_std_array<T> {};
template<class T>
struct is_std_array<T volatile const> : is_std_array<T> {};
template<typename T>
concept is_std_array_concept = is_std_array<T>::value;
I want all of this computation to be done at compile-time, so I have defined
template<std::size_t N>
std::size_t CompilerCompute() {
return N;
}
I should now be able to ComputeSize at compile time like so:
CompilerCompute<ComputeSize<Args...>()>()