Simulate the switch comportment in template specialization

冷暖自知 提交于 2020-01-06 03:59:28

问题


Here is the following code that shows a design I use. I made a wrapper class that encapsulates a template class. A method of the wrapper permits with a switch to choose the specialization I want :

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

Is it a way to have the same comportment, but using minimum and maximum constexpr values, and an automated version of the switch that loops on the range and does the good template for each value ?

Edit: I am searching for a runtime solution, since the parameter of setSize must be chosen by users through a GUI.


回答1:


As with all template metaprogramming problems, somehow index_sequence is involved. In this case, build up an inindex_sequence of the values you accept for the template parameter on Foo and iterate over them. For simplicity, here's a version that uses 0 as a minimum and 4 as a maximum:

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) will either give you a Foo<N> (if 0 <= N < 4) or an unset unique_ptr. If it gives you back nothing, you can just throw:

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

I'll leave generating index_sequence<1, 2, 3> as an exercise.




回答2:


You can create a init-time function table, such that each member of this table corresponds to one of your case statements above. Then setSize can use this table instead. By using constexpr min and max parameters, you can specify the bounds of this table with template specialisation, and populate it with template instantiations of 'maker' functions that create your Foo objects.

Here is the declaration of the function table code (all in the private section of 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;

Here is the definition of the function table creation, including specialisation for terminating 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;
}

And here is the complete code:

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



回答3:


I doubt it's possible. setSize can be made constexpr, since it's a non-static member of a class with non-trivial destructor.

And because it can't be made constexpr, all the template arguments in it's code will have to be hardcoded.



来源:https://stackoverflow.com/questions/37415812/simulate-the-switch-comportment-in-template-specialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!