在模板特化中模拟开关组件

Simulate the switch comportment in template specialization

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

#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 选择。

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

并且由于无法制作 constexpr,因此必须对代码中的所有模板参数进行硬编码。

与所有模板元编程问题一样,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<N>(如果 0 <= N < 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<1, 2, 3> 作为练习。

您可以创建一个初始化函数 table,这样 table 的每个成员都对应于您上面的一个 case 语句。然后 setSize 可以用这个 table 代替。通过使用 constexpr 最小和最大参数,您可以使用模板特化指定此 table 的边界,并使用创建 Foo 对象的 'maker' 函数的模板实例化来填充它。

这里是函数的声明table代码(都在FooWrapper的私有部分):

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;

这里是函数 table 创建的定义,包括终止情况的特化:

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;
}