【问题标题】:Specializing a function from a variadic template class特化来自可变参数模板类的函数
【发布时间】:2015-07-08 05:41:07
【问题描述】:

考虑一下这个非法代码:

template <int... Is>
struct Object {
    void foo() const;    
};

template <int... Js>
void Object<0, Js...>::foo() {/*Do whatever*/}

当第一个模板参数为 0 时,我们想特化 foo(),假设第二个参数为 3,第三个 int 为 1,我们也想特化 foo()。所以我找到的解决方案 (不确定它是否是最好的方法)如下:

#include <iostream>

template <int...> struct Foo;

template <int... Is>
struct Object {
    int ID;  // This member is just to illustrate the case when 'this' is needed in foo().
    friend struct Foo<Is...>;
    void foo() const {Foo<Is...>::execute(this);}  // Pass 'this' in case it is needed.
};

template <int... Is>
struct Foo<0, Is...> {
    static void execute (const Object<0, Is...>* object) {std::cout << "First int = 0, ID = " << object->ID << ".\n";}
};

template <int N, int... Is>
struct Foo<N, 3, Is...> {
    static void execute (const Object<N, 3, Is...>* object) {std::cout << "Second int = 3, ID = " << object->ID << ".\n";}
};

template <int M, int N, int... Is>
struct Foo<M, N, 1, Is...> {
    static void execute (const Object<M, N, 1, Is...>* object) {std::cout << "Third int = 1, ID = " << object->ID << ".\n";}
};


int main() {
    Object<0,5,8,2>{4}.foo();
    Object<4,3,2,5,3>{2}.foo();
    Object<4,2,1>{0}.foo();
}

首先,这个解决方案好用吗?接下来,如果我们尝试Object&lt;0,3,1,4&gt;{8}.foo();,就会出现问题,因为规范不完整。因此,假设最早匹配的专用 int 将始终优先。所以在这种情况下,Object&lt;0,3,1,4&gt;{8}.foo(); 应该因为 0 而运行第一个特化,而 Object&lt;9,3,1,4&gt;{8}.foo(); 因为 3 应该运行第二个特化,依此类推。如何执行该规则?

【问题讨论】:

    标签: c++ templates c++11 specialization


    【解决方案1】:

    我建议只使用if 语句。编译器可能无论如何都会优化它们(假设您启用了优化)。

    换句话说,只需执行以下操作:

    template <int... Js>
    void Object::foo() {
        std::array<int, sizeof...(Js)> args = {Js...}; // I _think_ this is the correct syntax to dump the parameter pack into an std::array.
        if(args.size() > 0 && args[0] == 0) {
            // First argument is 0, do whatever.
        } else {
            // It's not 0, do your other thing.
        }
    }
    

    您将获得几乎相同的效果,并且您的代码会更加清晰。

    【讨论】:

    • @celticminstrel 如果每个参数位置有 5 个专业化怎么办。您仍然建议使用一大串 if 语句吗?
    • 我认为即使那样它也可能更清楚。但是,我不确切知道您要做什么,所以我可能是错的。您可能会创建一个实用函数(如果可能,将其声明为constexpr),它简洁地将参数包与任何整数列表作为前缀进行比较。
    【解决方案2】:

    评论和提示。

    我的方法还可以。因为我们没有函数的部分模板特化,这就是我们所拥有的。

    然后关于 Object&lt;0,3,1,4&gt;{8}.foo() 这给出了模棱两可的部分专业化(在 Clang 3.6 上)。为了解决这个问题,我最终添加了另一个部分专业化

    template <int... Is>
    struct Foo<0, 3, Is...> {
        static void execute (const Object<0, 3, Is...>* object) {std::cout << "First int = 0, second = 3, ID = " << object->ID << ".\n";}
    };
    

    另一种可能性是 std::integer_sequence 混乱。现在只好放弃了,以下不是解决办法,只是开胃菜……

    #include <utility>
    #include <iostream>
    
    template <class S1, class S2>
    struct seq_lt
    {
        enum {value = 0} ;
    } ;
    
    template <int I1, int ...S1, int I2, int ...S2>
    struct seq_lt<std::integer_sequence<int, I1, S1...>,
                  std::integer_sequence<int, I2, S2...>>
    {
        enum {value = (I1 < I2 ? 1 : 0)} ;
    } ;
    
    
    int main(int argc, char *argv[])
    {
        std::integer_sequence<int, 1, 2, 3> seq1 ;
        std::integer_sequence<int, 2, 3> seq2 ;
    
        std::cout << "seq_lt " << seq_lt<decltype(seq1), decltype(seq2)>::value << std::endl ;
        std::cout << "seq_lt " << seq_lt<decltype(seq2), decltype(seq1)>::value << std::endl ;
    }
    

    【讨论】:

    • 感谢您的提示。但是当它们有许多独立的专业时,这样的组合的数量不会爆炸吗?没有更通用的方法吗?
    • 我正在考虑一种替代方法,将整数序列映射到一个类型,然后使用该类型重载调用。但这个想法还不清楚
    • 也许映射类型可以是MappedType&lt;A,B&gt; 的形式,其中A 是参数位置,B 是该位置的int 值?那么定义重载函数呢?你的意思是这样的吗?例如,如何将 {5,3,1,4} 数组映射到预期类型 MappedType&lt;1,3&gt;? (然后overloaded_function(MappedType&lt;1,3&gt;) 会调用第二个专业化?
    • struct Map&lt;Is...&gt; {using type = (NthValue&lt;0,Is...&gt;::value == 0) ? MappedType&lt;0,0&gt; : NthValue&lt;1,Is...&gt;::value == 3) ? MappedType&lt;1,3&gt; : NthValue&lt;2,Is...&gt;::value == 1) ? MappedType&lt;2,1&gt; : ... };,其中NthValue&lt;N,Is...&gt;::value 给出了Is... 的第N 个值类似的东西(并且全部在编译期间完成)?
    • 好吧,我的想法是整数序列(“开胃菜”中的一个)。但是我现在必须离开,而且我只能使用在线编译器对其进行测试(需要 C++14)
    【解决方案3】:

    这个解决方案的灵感来自 Marom 的第二个建议,部分灵感也来自 celticminstrel 的解决方案。

    #include <iostream>
    #include <type_traits>
    
    template <std::size_t, typename T, T...> struct NthValue;
    
    template <typename T, T First, T... Rest>
    struct NthValue<0, T, First, Rest...> : std::integral_constant<T, First> {};
    
    template <std::size_t N, typename T, T First, T... Rest>
    struct NthValue<N, T, First, Rest...> : NthValue<N - 1, T, Rest...> {};
    
    template <int... Is>
    struct Object {
        void foo() const {fooHelper (typename Map<Is...>::type{});}
    private:
        template <int...> struct Map;
        template <int, int> struct MappedType {};
        struct Default {};
        void fooHelper (const MappedType<0,0>&) const {std::cout << "First int = 0.\n";}
        void fooHelper (const MappedType<1,3>&) const {std::cout << "Second int = 3.\n";}
        void fooHelper (const MappedType<2,1>&) const {std::cout << "Third int = 1.\n";}
        void fooHelper (const Default&) const {std::cout << "Default case.\n";}
    };
    
    template <int... Ns>
    template <int... Is>
    struct Object<Ns...>::Map {
        using type = typename std::conditional<NthValue<0, int, Is...>::value == 0,
            MappedType<0,0>, 
            typename std::conditional<NthValue<1, int, Is...>::value == 3,
                MappedType<1,3>,
                typename std::conditional<NthValue<2, int, Is...>::value == 1,
                    MappedType<2,1>,
                    Default
                >::type
            >::type
        >::type;
    };
    
    int main() {
        Object<0,5,8,2>().foo();  // First int = 0.
        Object<4,3,2,5,3>().foo();  // Second int = 3.
        Object<4,2,1>().foo();  // Third int = 1.
        Object<0,3,1,4>().foo();  // First int = 0.
        Object<9,3,1,4>().foo();  // Second int = 3.
        Object<9,9,9>().foo();  // Default case.
    }
    

    也没有运行时开销。

    【讨论】:

    • @marom 我不知道template &lt;int...&gt; struct Map 应该在Object&lt;Is...&gt; 类内部还是外部,以及是否有更多的内存占用(甚至编译时开销)它在里面。但显然没有任何其他班级知道这件事的理由。
    • 把它放在里面......除非你能想出更一般的东西,值得一个独立的权利......
    • 我很好奇。我的回答启发了其中的哪一部分?
    • @celticminstrel 您的嵌套 if 语句。我的嵌套 std::conditional 模板就像运行时 if 语句的编译时版本。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 2021-07-26
    • 2011-11-15
    • 1970-01-01
    • 2021-10-01
    • 1970-01-01
    相关资源
    最近更新 更多