使用 SFINAE 启用基于包大小的部分专业化
Use SFINAE to enable a partial specialisation based on the size of a pack
我正在尝试制作一个在编译时使用字符的模板。在这种情况下,我想施加一个约束,即必须始终是给定字符数的精确倍数。
在没有完全匹配的情况下,我想在包的开头用 0 填充它们。
(顺便说一句,这背后的动机是想(在编译时,作为一个更大问题的一部分)添加对映射二进制和十六进制文字到 std::array<unsigned char, N>
的支持,除了填充之外,这工作得很好出不是字节倍数的东西)。
这是我为使填充起作用而尝试做的简化示例:
// 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>()
}
这击中了我的 static_assert
。问题是我们总是在这里匹配错误的专业化,这是我所期待的,因为它更专业化。
所以我环顾四周,发现了一些相同问题的示例 for a function,但据我所知,这些都不适用于此处。
我又尝试了一些东西,none 的效果和我希望的一样,首先是天真地 enable_if
在 sizeof...(Args)
我想要的地方:
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> {
// ...
};
据我所知,这是不合法的,而且肯定不适用于我的编译器 - 在我们需要查询 sizeof...(Args)
的地方,Args
还没有存在。
据我所知,我们不能在 pack 之后合法地添加另一个模板参数,这也失败了:
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> {
// ...
};
出现错误:
pad_params_try3.cc:17:8: error: default template arguments may not be used in partial specializations
我也在继承本身中尝试了 SFINAE,但这似乎不是一个合法的地方:
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 {
// ...
};
因为我们同时遇到了 static_assert
和失败,这是 enable_if
中的一个错误。
pad_params_try4.cc:17:8: error: no type named 'type' in 'struct std::enable_if<false, thingy<'[=15=]1', '[=15=]1', '[=15=]1', '[=15=]1'> >'
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);
据我所知,多阅读一些书 ,但这对我现在没有太大帮助。
我如何使用 C++14 中的 gcc 6.x 来解决这个问题?有没有比完全回到绘图板更简单的选择?
使用多重继承和执行填充的中间辅助结构的稍微不同的方法怎么样?
// 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_impl : thingy_impl<a, b, c, d>, thingy_impl<Args...> {
static_assert(sizeof...(Args) % 4 == 0);
// Do something with the value we just got and/or the tail as needed
typedef thingy_impl<a,b,c,d> head;
typedef thingy_impl<Args...> tail;
};
template<char a, char b, char c, char d>
struct thingy_impl<a,b,c,d> {
enum { value = (a << 24) | (b << 16) | (c << 8) | d };
};
template<int REMAINDER, char... Args>
struct padding;
template<char... Args>
struct padding<0,Args...> { using type = thingy_impl<Args...>; };
template<char... Args>
struct padding<1,Args...> { using type = thingy_impl<0,0,0,Args...>; };
template<char... Args>
struct padding<2,Args...> { using type = thingy_impl<0,0,Args...>; };
template<char... Args>
struct padding<3,Args...> { using type = thingy_impl<0,Args...>; };
template<char... Args>
struct thingy : padding<sizeof...(Args) % 4, Args...>::type { };
int main() {
thingy<1,1,1,1,1>(); // This should be equivalent to writing thingy<0,0,0,1,1,1,1,1>()
}
首先,C++ 17 中的一个简单解决方案,使用递归 if constexpr
辅助函数为您完成填充:
template<char ... Args>
auto getThingyPadded()
{
if constexpr (sizeof...(Args) % 4 != 0)
return getThingyPadded<0, Args...>();
else
return thingy<Args...>{};
}
为了制作这个 C++14,我们需要使用 SFINAE 而不是 if constexpr
。我们可以添加一个计算 sizeof...(Args)
的调用,以便我们规避您描述的问题:
template<bool B, class U = void>
using enableIfT = typename std::enable_if<B, U>::type;
template<std::size_t N, enableIfT<(N % 4 == 0)>* = nullptr, char ... Args>
auto getThingyPaddedHelper()
{
return thingy<Args...>{};
}
template<std::size_t N, enableIfT<(N % 4 != 0)>* = nullptr, char ... Args>
auto getThingyPaddedHelper()
{
return getThingyPaddedHelper<N+1, nullptr, 0, Args...>();
}
template<char ... Args>
auto getThingyPadded()
{
return getThingyPaddedHelper<sizeof...(Args), nullptr, Args...>();
}
另一种多重继承方法(来自 Jarod42 的更正和简化(谢谢!))。
#include <utility>
template <char a, char b, char c, char d, char ... Args>
struct t_base : public t_base<Args...>
{
typedef t_base<a,b,c,d> head;
typedef t_base<Args...> tail;
};
template <char a, char b, char c, char d>
struct t_base<a, b, c, d>
{ enum { value = (a << 24) | (b << 16) | (c << 8) | d }; };
template <typename, typename>
struct t_helper;
template <std::size_t ... Is, char ... Cs>
struct t_helper<std::index_sequence<Is...>,
std::integer_sequence<char, Cs...>>
: public t_base<(Is, '0')..., Cs...>
{ };
template <char ... Cs>
struct thingy :
public t_helper<std::make_index_sequence<(4u - sizeof...(Cs) % 4u) % 4u>,
std::integer_sequence<char, Cs...>>
{ };
int main ()
{
}
我会用 head/tail 去掉列表并使用 std::tuple
,结果是:
// No variadic here
template <char a, char b, char c, char d>
struct thingy {
enum { value = (a << 24) | (b << 16) | (c << 8) | d };
};
template <typename Seq, char... Cs>
struct thingies_impl;
template <std::size_t ...Is, char... Cs>
struct thingies_impl<std::index_sequence<Is...>, Cs...>
{
private:
static constexpr char get(std::size_t n)
{
constexpr char cs[] = {Cs...};
constexpr std::size_t paddingSize = (4 - (sizeof...(Cs) % 4)) % 4;
return (n < paddingSize) ? '[=10=]' : cs[n - paddingSize];
}
public:
using type = std::tuple<thingy<get(4 * Is),
get(4 * Is + 1),
get(4 * Is + 2),
get(4 * Is + 3)>...>;
};
template <char... Cs>
using thingies = thingies_impl<std::make_index_sequence<(sizeof...(Cs) + 3) / 4>, Cs...>;
我正在尝试制作一个在编译时使用字符的模板。在这种情况下,我想施加一个约束,即必须始终是给定字符数的精确倍数。
在没有完全匹配的情况下,我想在包的开头用 0 填充它们。
(顺便说一句,这背后的动机是想(在编译时,作为一个更大问题的一部分)添加对映射二进制和十六进制文字到 std::array<unsigned char, N>
的支持,除了填充之外,这工作得很好出不是字节倍数的东西)。
这是我为使填充起作用而尝试做的简化示例:
// 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>()
}
这击中了我的 static_assert
。问题是我们总是在这里匹配错误的专业化,这是我所期待的,因为它更专业化。
所以我环顾四周,发现了一些相同问题的示例 for a function,但据我所知,这些都不适用于此处。
我又尝试了一些东西,none 的效果和我希望的一样,首先是天真地 enable_if
在 sizeof...(Args)
我想要的地方:
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> {
// ...
};
据我所知,这是不合法的,而且肯定不适用于我的编译器 - 在我们需要查询 sizeof...(Args)
的地方,Args
还没有存在。
据我所知,我们不能在 pack 之后合法地添加另一个模板参数,这也失败了:
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> {
// ...
};
出现错误:
pad_params_try3.cc:17:8: error: default template arguments may not be used in partial specializations
我也在继承本身中尝试了 SFINAE,但这似乎不是一个合法的地方:
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 {
// ...
};
因为我们同时遇到了 static_assert
和失败,这是 enable_if
中的一个错误。
pad_params_try4.cc:17:8: error: no type named 'type' in 'struct std::enable_if<false, thingy<'[=15=]1', '[=15=]1', '[=15=]1', '[=15=]1'> >'
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);
据我所知,多阅读一些书
我如何使用 C++14 中的 gcc 6.x 来解决这个问题?有没有比完全回到绘图板更简单的选择?
使用多重继承和执行填充的中间辅助结构的稍微不同的方法怎么样?
// 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_impl : thingy_impl<a, b, c, d>, thingy_impl<Args...> {
static_assert(sizeof...(Args) % 4 == 0);
// Do something with the value we just got and/or the tail as needed
typedef thingy_impl<a,b,c,d> head;
typedef thingy_impl<Args...> tail;
};
template<char a, char b, char c, char d>
struct thingy_impl<a,b,c,d> {
enum { value = (a << 24) | (b << 16) | (c << 8) | d };
};
template<int REMAINDER, char... Args>
struct padding;
template<char... Args>
struct padding<0,Args...> { using type = thingy_impl<Args...>; };
template<char... Args>
struct padding<1,Args...> { using type = thingy_impl<0,0,0,Args...>; };
template<char... Args>
struct padding<2,Args...> { using type = thingy_impl<0,0,Args...>; };
template<char... Args>
struct padding<3,Args...> { using type = thingy_impl<0,Args...>; };
template<char... Args>
struct thingy : padding<sizeof...(Args) % 4, Args...>::type { };
int main() {
thingy<1,1,1,1,1>(); // This should be equivalent to writing thingy<0,0,0,1,1,1,1,1>()
}
首先,C++ 17 中的一个简单解决方案,使用递归 if constexpr
辅助函数为您完成填充:
template<char ... Args>
auto getThingyPadded()
{
if constexpr (sizeof...(Args) % 4 != 0)
return getThingyPadded<0, Args...>();
else
return thingy<Args...>{};
}
为了制作这个 C++14,我们需要使用 SFINAE 而不是 if constexpr
。我们可以添加一个计算 sizeof...(Args)
的调用,以便我们规避您描述的问题:
template<bool B, class U = void>
using enableIfT = typename std::enable_if<B, U>::type;
template<std::size_t N, enableIfT<(N % 4 == 0)>* = nullptr, char ... Args>
auto getThingyPaddedHelper()
{
return thingy<Args...>{};
}
template<std::size_t N, enableIfT<(N % 4 != 0)>* = nullptr, char ... Args>
auto getThingyPaddedHelper()
{
return getThingyPaddedHelper<N+1, nullptr, 0, Args...>();
}
template<char ... Args>
auto getThingyPadded()
{
return getThingyPaddedHelper<sizeof...(Args), nullptr, Args...>();
}
另一种多重继承方法(来自 Jarod42 的更正和简化(谢谢!))。
#include <utility>
template <char a, char b, char c, char d, char ... Args>
struct t_base : public t_base<Args...>
{
typedef t_base<a,b,c,d> head;
typedef t_base<Args...> tail;
};
template <char a, char b, char c, char d>
struct t_base<a, b, c, d>
{ enum { value = (a << 24) | (b << 16) | (c << 8) | d }; };
template <typename, typename>
struct t_helper;
template <std::size_t ... Is, char ... Cs>
struct t_helper<std::index_sequence<Is...>,
std::integer_sequence<char, Cs...>>
: public t_base<(Is, '0')..., Cs...>
{ };
template <char ... Cs>
struct thingy :
public t_helper<std::make_index_sequence<(4u - sizeof...(Cs) % 4u) % 4u>,
std::integer_sequence<char, Cs...>>
{ };
int main ()
{
}
我会用 head/tail 去掉列表并使用 std::tuple
,结果是:
// No variadic here
template <char a, char b, char c, char d>
struct thingy {
enum { value = (a << 24) | (b << 16) | (c << 8) | d };
};
template <typename Seq, char... Cs>
struct thingies_impl;
template <std::size_t ...Is, char... Cs>
struct thingies_impl<std::index_sequence<Is...>, Cs...>
{
private:
static constexpr char get(std::size_t n)
{
constexpr char cs[] = {Cs...};
constexpr std::size_t paddingSize = (4 - (sizeof...(Cs) % 4)) % 4;
return (n < paddingSize) ? '[=10=]' : cs[n - paddingSize];
}
public:
using type = std::tuple<thingy<get(4 * Is),
get(4 * Is + 1),
get(4 * Is + 2),
get(4 * Is + 3)>...>;
};
template <char... Cs>
using thingies = thingies_impl<std::make_index_sequence<(sizeof...(Cs) + 3) / 4>, Cs...>;