I'm trying to make a template that works with characters, at compile time. In this instance I want to impose a constraint that there must always be an exact multiple of some number of characters given.
In the case where there isn't an exact match I want to pad them with 0 at the head of the pack.
(As an aside the motivation behind this is wanting to (at compile time, as part of a bigger problem) add support for mapping binary and hexadecimal literals on to std::array<unsigned char, N>
, this works beautifully except for padding out the things which aren't a multiple of bytes).
Here's a simplified example of what I'm trying to do to get the padding to work:
// Thingy operates on N*4 chars - if that's not met use inheritance to 0 pad until it is met.
template<char ...Args>
struct thingy : thingy<0, Args...> {
// All we do here is recursively add one more 0 via inheritance until the N*4 rule is met
};
// This specialisation does the real work, N=1 case only
template<char a, char b, char c, char d>
struct thingy<a,b,c,d> {
enum { value = (a << 24) | (b << 16) | (c << 8) | d };
};
// This handles chunking the N*4 things into N cases of work. Does work along the way, only allowed for exact N*4 after padding has happened.
template <char a, char b, char c, char d, char ...Args>
struct thingy<a,b,c,d,Args...> : thingy<a,b,c,d> {
static_assert(sizeof...(Args) % 4 == 0); // PROBLEM: this is a we're a better match than the template that pads things, how do we stop that?
// Do something with the value we just got and/or the tail as needed
typedef thingy<a,b,c,d> head;
typedef thingy<Args...> tail;
};
int main() {
thingy<1,1,1,1,1>(); // This should be equivalent to writing thingy<0,0,0,1,1,1,1,1>()
}
This hits my static_assert
. The issue is we always match the wrong specialisation here, which I was expecting because it's more specialised.
So I looked around and found some examples of the same problem for a function, but as far as I can see neither of those are applicable here.
I tried a few more things out, none of which worked quite how I'd hoped, first up was naively enable_if
on sizeof...(Args)
exactly where I wanted it:
template <char a, char b, char c, char d, typename std::enable_if<sizeof...(Args) % 4 == 0, char>::type ...Args>
struct thingy<a,b,c,d,Args...> : thingy<a,b,c,d> {
// ...
};
This isn't legal though as far as I can tell and certainly doesn't work on my compilers - at the point where we need to query sizeof...(Args)
the Args
doesn't yet exist.
We can't legally add another template argument after the pack either as far as I can tell, this also failed:
template <char a, char b, char c, char d, char ...Args, typename std::enable_if<sizeof...(Args) % 4 == 0, int>::type=0>
struct thingy<a,b,c,d,Args...> : thingy<a,b,c,d> {
// ...
};
with an error:
pad_params_try3.cc:17:8: error: default template arguments may not be used in partial specializations
I also tried SFINAE in the inheritance itself, but that doesn't seem to be a legitimate place to do it:
template <char a, char b, char c, char d, char ...Args>
struct thingy<a,b,c,d,Args...> : std::enable_if<sizeof...(Args) % 4 == 0, thingy<a,b,c,d>>::type {
// ...
};
In that we hit both the static_assert
and an failure which is an error in enable_if
.
pad_params_try4.cc:17:8: error: no type named 'type' in 'struct std::enable_if<false, thingy<'\001', '\001', '\001', '\001'> >'
struct thingy<a,b,c,d,Args...> : std::enable_if<sizeof...(Args) % 4 == 0, thingy<a,b,c,d>>::type {
^~~~~~~~~~~~~~~~~~~~~~~
pad_params_try4.cc:18:5: error: static assertion failed
static_assert(sizeof...(Args) % 4 == 0);
As far as I can make out from reading around a bit more this might even be considered a defect, but that doesn't help me out much right now.
How can I work around this, with what I've got in C++14, gcc 6.x? Is there a simpler option than going back to the drawing board completely?