Home > Mobile >  Allow Only Explicit Specialization of Template Class
Allow Only Explicit Specialization of Template Class

Time:06-18

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:

godbolt

#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.

godbolt

#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.

godbolt

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.

godbolt

#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
  • Related