How to use fold expressions on operator comma isn't even touched on.
How to use fold expressions on operator comma isn't even touched on.
https://www.scs.stanford.edu/~dm/blog/param-pack.html#comma-...
C++ template / overload resolution involves some very convoluted logic. And it can take some work to even notice if / how it went differently than a programmer expected.
Programmers typically have logging, interactive debugging, etc. to investigate complicated algorithms like this. But (almost?) no C++ compiler provides such tooling for investigating the behavior of this algorithm.
IMHO it's an area where C++ tooling could serious use improvement.
My personal theory is that compiler authors have not provided a template equivalent, because they are embarrassed about the code that's being produced.
(If you disagree, prove me wrong!)
Template don't "produce code". They get expanded well after parsing. There is no code to be embarrassed for.
Also you can ask GCC to dump any intermediate state: https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html.
https://cppinsights.io/lnk?code=dm9pZCBmb28oYXV0bykge30KCnZv...
I found the source at https://github.com/andreasfertig/cppinsights
This pattern is getting easier and easier in modern C++ as more and more thing are made constexpr.
Aside from the fact that the articles shows many use cases where packs and fold expressions obviate the need for recursion, what's so bad about recursive template instantiations?
The functionality can be computed as a simple constexpr function.
A string as a char template parameter pack is a textbook example of an antipattern.
A place where this kind of pattern is useful is in having a generic accessor type for fields, and wanting to extend it to tuples. So for example accessor(x) might return x.field, and accessor.name might be "field." To make it work for tuples, you need a string for every possible tuple index. E.g.:
template<typename T, size_t N> struct tuple_accessor;
template<typename...T, size_t N>
struct tuple_accessor<std::tuple<T...>, N> {
constexpr decltype(auto) operator(auto &&t) const {
return get<N>(std::forward<decltype(t)>(t));
}
static constexpr const char *name = index_string<N>();
}; #include <array>
#include <tuple>
constexpr std::array<char, 11> itos(unsigned n) {
std::array<char, 11> s;
unsigned i = 0;
while (n) {
s[i++] = '0' + (n % 10);
n /= 10;
}
s[i] = '\0';
return s;
}
template<auto V>
struct static_value {
static constexpr auto value = V;
};
template<class T, unsigned N>
struct tuple_accessor;
template<class...T, unsigned N>
struct tuple_accessor<std::tuple<T...>, N> {
static constexpr const char *name = static_value<itos(N)>::value.data();
};tuple_accessor::name is a const char* which is what you asked for. static_value here is an implementation detail and the type of its members is irrelevant.
<size_t ...Idxs>
construct work, when we needed same_as<char>?<size_t ...Idxs> works because it introduces a pack that is a template parameter, so it naturally gets templated on the size of the pack (as well as on the values).
I guess an other historical issue for the grammar is that without the parameter name, `void foo(size_t ...)` is already valid grammar, and is equivalent to `void foo(size_t, ...)`, a C-style variadic.