I have a family of classes in a library that can be "installed" in another class, either as a single member or as an array, dependent on the application. The arrays are indexed with an integer or enum type, dependent on the application (void
is used when an array is not meaningful). The installable class itself has no control over the indexing; the application using the class defines the index.
However, I imagine that unwanted specialziations could be created by a typo and compile OK.
I want to constrain the indexing types to only the ones intended for the application, by making the client signal back to the library which associations are OK. I couldn't see a pure template metaprogramming approach, so I thought I'd exploit ODR and explicit specialization of class members.
namespace foo {
template <class P, class ROLE>
struct association
{
static_assert(std::is_enum_v<ROLE>||std::is_integral_v<ROLE>);
static const bool allowed();
};
template <class T>
class bar final
{
public:
bar() = default;
~bar() = default;
};
void do_something() {}
template <class I, class ROLE>
void install(I &&i, ROLE r)
{
if (association<std::decay_t<I>, ROLE>::allowed()) do_something();
}
template <class I>
void install(I &&i)
{
if (association<std::decay_t<I>, void>::allowed()) do_something();
}
}
With the following sample use:
// declare the indexing type
enum myindex { min=0, max=3 };
int main() {
foo::bar<int> foobar;
foo::install(foobar, myindex::min);
return 0;
}
There should be a linker error unless we also add
// add a definiton to make the association with myindex OK
template <> const bool foo::association<bar<int>, myindex>::allowed() { return true; }
In the full code, the value of "allowed" doesn't matter, only the existence of a definition does.
It's a pretty cryptic way of saying "this association is OK", but it works. At least, if you fully specialize association
. But this is where the "sometimes" comes in: Some of the templates are supposed to work with any indexing type. It's a pain to make the library user write out specializations for these templates. But the following
template <class T, class ROLE> const bool foo::association<foo::bar<T>, ROLE>::allowed () { return true; }
is a compiler error, because it's not a full specialization.
Is there a way to fully define association::allowed()
for all combinations of bar
specializations with any ROLE
, but force the user to define it for other templates?
If not, is there a better approach that accomplishes this goal? (Hopefull, something that can be used with static_assert
because what I have now is charitably called 'clunky').
Remember, myindex
cannot be rolled into the library. (I'm sticking to C 17 for the time being).
CodePudding user response:
This case seems like a good place for template variables. One of their use is to make them a kind of a map - which in this case would greatly increase readability of the client code and move everything to compile-time. If you just want to break compilation the following should be fine:
#include <iostream>
#include <type_traits>
// make_association is a variable template which works as a compile-time map
// default value is false_type = no relation
template <typename I,typename ROLE>
constexpr std::false_type make_association;
// isAssociated used as a compile-time "function"
template <typename I, typename R>
using isAssociated = decltype(make_association<std::decay_t<I>,std::decay_t<R>>);
template <typename I, typename ROLE>
void install( I &&i, ROLE r)
{
static_assert(isAssociated<I,ROLE>(), "make_association<I, ROLE> not registered");
static_assert(std::is_enum_v<ROLE> || std::is_integral_v<ROLE> );
std::cout<<"Entered install( I &&, ROLE r)"<<std::endl;
}
template <typename I>
void install( I &&i)
{
static_assert(isAssociated<I,void>(), "make_association<I, void> not registered");
std::cout<<"Entered install( I &&)"<<std::endl;
}
enum WrongIndexT { ok = 1};
enum IndexT { min=0, max=3 };
class ObjT final {};
// a class for which any index works
template <class T> class Any final {};
template <class T, class Index>
constexpr std::true_type make_association<Any<T>, Index>;
// here the relations are set using variable template specialization;
template<> constexpr std::true_type make_association<ObjT, IndexT>;
template<> constexpr std::true_type make_association<ObjT, void>;
int main() {
ObjT f1,f2,f3;
install(f1, IndexT::min);
install(f2);
install(Any<int>{}, WrongIndexT::ok); // OK
install(f1, WrongIndexT::ok); // compilation error
return 0;
}