1

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.

Boiethios
  • 38,438
  • 19
  • 134
  • 183

3 Answers3

3

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I tried to understand `(void)swallow{0, (void(N == Is ? mfoo = std::make_unique>() : mfoo), 0)... };` ; but I cannot get it... :/ I do not just understand the syntax. Could you explain it in your answer ? – Boiethios May 24 '16 at 16:53
  • I understand you create a temporary array of `int`s, but what is `0, (void(N == Is ? mfoo = std::make_unique>() : mfoo), 0)...` ?? – Boiethios May 24 '16 at 16:56
1

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;
}
Smeeheey
  • 9,906
  • 23
  • 39
  • Wow. I do not fully understand the code. Are they different objects when I recall setSize with the same number ? `a.setSize(2u); a.setSize(2u); // after the second call is it the same object ?` – Boiethios May 24 '16 at 15:31
  • Each call to `setSize()` calls a `fooMaker` function specialised for a particular `n`. For example, if you call `setSize(2)` your `mfoo` object is assigned the return value of `fooMaker<2>()`. So each call causes a new object to be created. Obviously you can modify it if this is not desired behaviour - its just consistent with your previous `switch` statement – Smeeheey May 24 '16 at 15:36
  • The use of the function table allows you to dispatch to a template function `fooMaker` based on a dynamic parameter, instead of a compile-time one. – Smeeheey May 24 '16 at 15:37
  • That is the desired behavior, yes. – Boiethios May 24 '16 at 15:44
0

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137