【问题标题】:Use SFINAE to enable a partial specialisation based on the size of a pack使用 SFINAE 启用基于包大小的部分专业化
【发布时间】:2019-02-15 11:13:42
【问题描述】:

我正在尝试在编译时制作一个与字符一起使用的模板。在这种情况下,我想施加一个约束,即必须始终存在给定字符数的精确倍数。

在没有完全匹配的情况下,我想在包的开头用 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,但据我所知,这些都不适用于这里。

我尝试了更多的东西,但都没有达到我希望的效果,首先是天真的 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 尚不存在。

据我所知,我们不能在包之后合法地添加另一个模板参数,这也失败了:

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_assertenable_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);

据我所知,通过阅读更多 this might even be considered a defect 可以看出,但这对我现在没有多大帮助。

如何使用 C++14、gcc 6.x 中的内容解决这个问题?有没有比完全回到绘图板更简单的选择?

【问题讨论】:

  • 您可以使用一个辅助函数来解决此问题,该函数在实际打包之前使用您想要 SFINAE 的计算值调用。让我写一个答案。

标签: c++ c++14 template-meta-programming sfinae


【解决方案1】:

使用多重继承和执行填充的中间辅助结构体的方法略有不同如何?

// 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>()
}

Demo, with a diagnostic output.

【讨论】:

  • 你不能简化它以避免单独显式处理 1,2,3 填充情况吗? IE。 padding 和 padding 就足够了。
  • @Flexo:可能:template&lt;int REMAINDER, char... Args&gt; struct padding : padding&lt;(REMAINDER + 1) % 4, 0, Args...&gt; {}; + 0 的当前专业化。
  • 这是我采用的解决方案,我采用了仅处理 0N 填充的情况,因为它允许我使用一些特征进一步概括填充要求
【解决方案2】:

首先,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...>();
}

Demo!

【讨论】:

    【解决方案3】:

    我会去掉带有头/尾的列表并使用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) ? '\0' : 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...>;
    

    Demo

    【讨论】:

    • 我完全错过了解包运算符的另一种使用方式
    【解决方案4】:

    另一种多重继承方法(对 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 ()
     {
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多