问题
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