Search code examples
c++templatessfinaeenable-if

How to arbitrarily enable or disable class method based on specific type?


I have a class like this:

struct X
{
    enum Type { INT, FLOAT };
    using val_t = std::tuple<int, float>;

    X(Type t) : type(t) {}

    Type type;

    template<typename T>
    X& operator =(T x)
    {
        // ???
        static_assert(T is the same as `type');

        // ???
        std::get<type>(val) = x;

        return *this;
    }

    val_t val;
};

Is it possible to assert at compile time if user tries to assign incompatible value?

For example:

X x1(X::INT);
x1 = 5; // OK
x1 = 3.14; // compilation error

Note: I prefer keeping the class as not a template because I need to keep its instances in collections (like std::vector etc).


Solution

  • You cannot: the value of type_ is run time data, compilation errors are not determined at runtime.

    You could do:

    enum Type { INT, FLOAT };
    template<Type type_>
    struct X {
      using val_t = std::tuple<int, float>;
    
    
      template<typename T>
      X& operator =(T x) {
        // ???
        static_assert((type_==INT&&std::is_same<T,int>{})||(type_==FLOAT&&std::is_same<T,float>{}),
          "types do not match"
        );
        std::get<T>(val) = x;
    
        return *this;
      }
    
      val_t val;
    };
    X<INT> x1;
    x1 = 5; // OK
    x1 = 3.14; // compilation error
    

    but I do not see much point.

    One way would be to have a base type that does not do the checking just stores state, and a derived that knows its type.

    struct Base{
      enum {INT,FLOAT} Type;
      // etc
    };
    template<Base::Type type>
    struct Derived:private Base{
      Derived():Base(type){}
      using Base::some_method; // expose base methods
      Base& get_base()&{return *this;}
      Base get_base()&&{return std::move(*this);}
      Base const& get_base()const&{return *this;}
    
      template<class T>
      Derived& operator=( T o){
        static_assert((type_==INT&&std::is_same<T,int>{})||(type_==FLOAT&&std::is_same<T,float>{}),
          "types do not match"
        );
        Base::operator=(std::move(o));
        return *this;
      }
    };
    

    Base does not check, at best it runtime asserts. Derived checks at compile time.

    Niw when you know the type statically at compile time you use Derived<INT> d;; when you do not, or need to forget, use .get_base() or a Base b(type_enum_val);.