【问题标题】:Simulate the switch comportment in template specialization模拟模板特化中的 switch 语句
【发布时间】:2016-05-24 13:57:20
【问题描述】:

以下代码显示了我使用的设计。我制作了一个封装模板类的包装类。包装器的一种方法允许通过开关选择我想要的专业:

#include <memory>
#include <string>

/* Interface
 */
struct IFoo
{
    virtual void lol(void) = 0;
};

/* Template Specialization
 */
template<std::size_t N>
class Foo : public IFoo
{
    void lol(void) {}
};

/* Wrapper for the template
 */
class FooWrapper : public IFoo
{
    std::unique_ptr<IFoo>   mfoo;

    public:
    void setSize(std::size_t size)
    {
        switch (size) // how to 'simulate' this switch comportement
                      // with min/max constexpr variables ?
        {
            case 1u:
                mfoo = std::make_unique< Foo<1u> >();
                break ;
            case 2u:
                mfoo = std::make_unique< Foo<2u> >();
                break ;
            case 3u:
                mfoo = std::make_unique< Foo<3u> >();
                break ;
            default:
                throw std::runtime_error(std::to_string(size) + " not supported.");
        }
    }

    FooWrapper(std::size_t size)
    {
        this->setSize(size);
    }

    void lol(void)
    {
        mfoo->lol();
    }
};

int main(void)
{
    FooWrapper  a(3u);  // ok
    a.setSize(2u);      // ok
    a.setSize(0u);      // will throw an exception at runtime

    return EXIT_SUCCESS;
}

这是一种具有相同行为的方法,但使用最小和最大 constexpr 值,以及在范围上循环并为每个值做良好模板的自动版本的开关?

编辑:我正在寻找运行时解决方案,因为 setSize 的参数必须由用户通过 GUI 选择。

【问题讨论】:

    标签: c++ templates metaprogramming


    【解决方案1】:

    与所有模板元编程问题一样,不知何故涉及index_sequence。在这种情况下,为您在Foo 上接受的模板参数值构建一个 inindex_sequence 并迭代它们。为简单起见,这里有一个使用0 作为最小值和4 作为最大值的版本:

    template <std::size_t... Is>
    std::unique_ptr<IFoo> create(std::size_t N, std::index_sequence<Is...> ) {
        std::unique_ptr<IFoo> mfoo;
    
        using swallow = int[];        
        (void)swallow{0,
            (void(N == Is ? mfoo = std::make_unique<Foo<Is>>() : mfoo), 0)...
        };
    
        return mfoo;
    }
    
    std::unique_ptr<IFoo> create(std::size_t N) {
        return create(N, std::make_index_sequence<4>{} );
    }
    

    create(N) 会给你一个Foo&lt;N&gt;(如果是0 &lt;= N &lt; 4)或一个未设置的unique_ptr。如果它什么也没给你,你可以扔:

    void setSize(std::size_t size) {
        auto new_foo = create(size); // or call with index_sequence
        if (!new_foo) {
            // throw
        }
    
        mfoo = std::move(new_foo);
    }
    

    我将把生成index_sequence&lt;1, 2, 3&gt; 作为练习。

    【讨论】:

    • 我试图理解(void)swallow{0, (void(N == Is ? mfoo = std::make_unique&lt;Foo&lt;Is&gt;&gt;() : mfoo), 0)... };;但我无法理解......:/我不只是理解语法。你能在你的回答中解释一下吗?
    • 我知道你创建了一个ints 的临时数组,但是0, (void(N == Is ? mfoo = std::make_unique&lt;Foo&lt;Is&gt;&gt;() : mfoo), 0)... 是什么??
    【解决方案2】:

    您可以创建一个 init-time 函数表,以便该表的每个成员对应于您上面的一个案例语句。然后setSize 可以改用这个表。通过使用constexpr min 和 max 参数,您可以使用模板专业化指定此表的边界,并使用创建 Foo 对象的“制造商”函数的模板实例化填充它。

    这里是函数表代码的声明(都在FooWrapper的private部分):

    template<unsigned i>
    static std::unique_ptr<IFoo> fooMaker()
    {
        return std::make_unique< Foo<i> >();
    }
    
    static constexpr unsigned FOO_MIN = 1;
    static constexpr unsigned FOO_MAX = 3;
    
    using FooMakerFn = std::unique_ptr<IFoo>();
    
    template<unsigned min>
    static std::array<FooMakerFn*, FOO_MAX-FOO_MIN-1>& initFooFnTable(std::array<FooMakerFn*, FOO_MAX-FOO_MIN-1>& fnTable);
    
    static std::array<FooMakerFn*, FOO_MAX-FOO_MIN-1> fooFnTable;
    

    这里是函数表创建的定义,包括终止case的特化:

    std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1> FooWrapper::fooFnTable = FooWrapper::initFooFnTable<FooWrapper::FOO_MIN>(FooWrapper::fooFnTable);
    
    template<unsigned min>
    std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1>& FooWrapper::initFooFnTable(std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FOO_MIN-1>& fnTable)
    {
        fnTable[min - FOO_MIN] = FooWrapper::fooMaker<min>;
        return initFooFnTable<min+1>(fnTable);
    }
    
    template<>
    std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1>& FooWrapper::initFooFnTable<FooWrapper::FOO_MAX>(std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1>& fnTable)
    {
        fnTable[FOO_MAX - FOO_MIN] = FooWrapper::fooMaker<FooWrapper::FOO_MAX>;
        return fnTable;
    }
    

    这是完整的代码:

    #include <memory>
    #include <string>
    #include <iostream>
    
    /* Interface
     */
    struct IFoo
    {
        virtual void lol(void) = 0;
    };
    
    /* Template Specialization
     */
    template<std::size_t N>
    class Foo : public IFoo
    {
        void lol(void) 
        {
            std::cout << "Lol: " << N << std::endl;
        }
    };
    
    /* Wrapper for the template
     */
    class FooWrapper : public IFoo
    {
        std::unique_ptr<IFoo>   mfoo;
    
        public:
        void setSize(std::size_t size)
        {
            if(size >= FOO_MIN && size <= FOO_MAX)
                mfoo = fooFnTable[size - FOO_MIN]();
            else
                throw std::runtime_error(std::to_string(size) + " not supported.");
        }
    
        FooWrapper(std::size_t size)
        {
            this->setSize(size);
        }
    
        void lol(void)
        {
            mfoo->lol();
        }
    
    private:
    
        template<unsigned i>
        static std::unique_ptr<IFoo> fooMaker()
        {
            return std::make_unique< Foo<i> >();
        }
    
        static constexpr unsigned FOO_MIN = 1;
        static constexpr unsigned FOO_MAX = 3;
    
        using FooMakerFn = std::unique_ptr<IFoo>();
    
        template<unsigned min>
        static std::array<FooMakerFn*, FOO_MAX-FOO_MIN-1>& initFooFnTable(std::array<FooMakerFn*, FOO_MAX-FOO_MIN-1>& fnTable);
    
        static std::array<FooMakerFn*, FOO_MAX-FOO_MIN-1> fooFnTable;
    };
    
    std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1> FooWrapper::fooFnTable = FooWrapper::initFooFnTable<FooWrapper::FOO_MIN>(FooWrapper::fooFnTable);
    
    template<unsigned min>
    std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1>& FooWrapper::initFooFnTable(std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FOO_MIN-1>& fnTable)
    {
        fnTable[min - FOO_MIN] = FooWrapper::fooMaker<min>;
        return initFooFnTable<min+1>(fnTable);
    }
    
    template<>
    std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1>& FooWrapper::initFooFnTable<FooWrapper::FOO_MAX>(std::array<FooWrapper::FooMakerFn*, FooWrapper::FOO_MAX-FooWrapper::FOO_MIN-1>& fnTable)
    {
        fnTable[FOO_MAX - FOO_MIN] = FooWrapper::fooMaker<FooWrapper::FOO_MAX>;
        return fnTable;
    }
    
    int main(void)
    {
        FooWrapper  a(3u);  // ok
        a.setSize(2u);      // ok
        a.setSize(0u);      // will throw an exception at runtime
    
        a.lol();
    
        return EXIT_SUCCESS;
    }
    

    【讨论】:

    • 哇。我不完全理解代码。当我用相同的数字回忆 setSize 时,它​​们是不同的对象吗? a.setSize(2u); a.setSize(2u); // after the second call is it the same object ?
    • 每次调用setSize() 都会调用一个专用于特定nfooMaker 函数。例如,如果您调用setSize(2),您的mfoo 对象将被分配fooMaker&lt;2&gt;() 的返回值。因此,每次调用都会创建一个新对象。显然,如果这不是您想要的行为,您可以对其进行修改 - 它与您之前的 switch 声明一致
    • 函数表的使用允许您基于动态参数而不是编译时参数调度到模板函数fooMaker
    • 这是期望的行为,是的。
    【解决方案3】:

    我怀疑这是可能的。 setSize 可以设为 constexpr,因为它是具有非平凡析构函数的类的非静态成员。

    而且由于不能将其设为constexpr,因此其代码中的所有模板参数都必须进行硬编码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-30
      • 1970-01-01
      • 2013-03-08
      • 1970-01-01
      • 2018-02-26
      • 2011-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多