I want to limit a template class to the only explicit implementations. I can do this with many functions as:
template<typename T>
static T getEnumFromString(const std::string& in_string) = delete; // only allow templates we define (catches them at compile time)
template<> static A getEnumFromString(const std::string& in_string);
template<> static B getEnumFromString(const std::string& in_string);
I have a template class, that stores enum/string in maps, where I use a static_assert
to limit to only enums, but this is not limiting enough due to enums visible (only want those defined in enclosing class):
template<typename TEnum>
class kvEnumHelper {
static_assert(std::is_enum_v<TEnum>);
public:
std::string getStringFromEnum(const TEnum& in_enum) const;
TEnum getEnumFromString(const std::string& in_string) const;
protected:
kvEnumHelper();
void initialize(std::vector<EnumTextPair<TEnum>> enumInitializer);
private:
std::map<TEnum, std::string> mapEnumToString;
std::map<std::string, TEnum> mapStringToEnum;
};
I implement explicit class for each enum, (where the enum/string pairs are defined in the constructor) e.g.:
class AenumHelper : public kvEnumHelper<tA> {
using myEnum = tA;
public:
AenumHelper();
};
inline static AenumHelper aEnumHelper;
The issue is that the static assert is insufficient, as there are many other enums in scope that do not apply here. I have tried permutations like:
template<typename TEnum> kvEnumHelper<TEnum> = delete;
template<typename TEnum> class kvEnumHelper<TEnum> = delete;
template<> class kvEnumHelper<> = delete;
kvEnumHelper = delete;
//
Is there a way, similar to the function case, to allow explicit specialization for my kvEnumHelper<>
class?
CodePudding user response:
I want to limit a template class to the only explicit implementations.
Not sure to understand exactly what you want, for functions case, template don't provide code, and each specialization has to provide its own.
A nearly equivalent for class would be declaration without definition:
template <typename E>
requires(std::is_enum_v<E>) // C 20 contraint
class kvEnumHelper;
template<>
class kvEnumHelper<ta> {
static_assert(std::is_enum_v<ta>);
public:
std::string getStringFromEnum(const ta& in_enum) const;
ta getEnumFromString(const std::string& in_string) const;
protected:
kvEnumHelper();
void initialize(std::vector<EnumTextPair<ta>> enumInitializer);
private:
std::map<ta, std::string> mapEnumToString;
std::map<std::string, ta> mapStringToEnum;
};
static_assert
is insufficient, as there are many other enums in scope that do not apply here
You might still be more specific in your static_assert
, as listing the allowed enum types.
CodePudding user response:
There are a few ways how you could accomplish this.
1. Explicitly list the acceptable enums
One way would be to explicitly list the acceptable enums in your static_assert
:
#include <type_traits>
template<class T, class... Other>
constexpr bool is_same_one_of = (std::is_same_v<T, Other> || ...);
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<class T>
class kvEnumHelper {
static_assert(
is_same_one_of<T, Enum1, Enum2 /* , ... more enum types ... */>,
"T must be either Enum1 or Enum2"
);
/* ... actual implementation ... */
};
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
2. Inherit implementation
Another option would be to move the actual implementation into a separate class and only make the specializations inherit from the implementation class.
#include <type_traits>
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<class T>
class kvEnumHelper {
static_assert(
!std::is_same_v<T, T>, // always false, but only when actually instanciated
"Enum Class is not supported"
);
};
template<class TEnum>
class kvEnumHelperImpl {
/* ... actual implementation ... */
};
template<> class kvEnumHelper<Enum1> : public kvEnumHelperImpl<Enum1> {};
template<> class kvEnumHelper<Enum2> : public kvEnumHelperImpl<Enum2> {};
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
3. Using an additional trait
Yet another alternative would be to use a trait that can specialized for the enum types you would want to be usable with kvEnumHelper
.
template <class T>
constexpr bool allow_enum_helper = false;
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<class T>
class kvEnumHelper {
static_assert(
allow_enum_helper<T>,
"Enum Class is not supported"
);
/* ... actual implementation ... */
};
template<>
constexpr bool allow_enum_helper<Enum1> = true;
template<>
constexpr bool allow_enum_helper<Enum2> = true;
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
If you already have a function like getEnumFromString
that is deleted and has specializations for the allowable enum types you could use that to detect if kvEnumHelper<T>
should be allowed by detecting if the function is deleted or not.
#include <string>
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<typename T>
T getEnumFromString(const std::string& in_string) = delete; // only allow templates we define (catches them at compile time)
template<> Enum1 getEnumFromString(const std::string& in_string);
template<> Enum2 getEnumFromString(const std::string& in_string);
template<class T>
constexpr bool allow_enum_helper = requires { getEnumFromString<T>(std::string{}); };
template<class T>
class kvEnumHelper {
static_assert(
allow_enum_helper<T>,
"Enum Class is not supported"
);
/* ... actual implementation ... */
};
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error