1

I am designing a Dynamic class that can store any kind of value. It is defined as:

class Dynamic {
 private:
  Type type_;
  std::uint64_t data_;

 public:
  Dynamic();
  ... 
}

where Type is an enum that contains ids of all the types I can store in the Dynamic object : unsigned char, signed char, char, ..., unsigned short, short, std::size_t, std::ptrdiff_t, float, double, std::vector<unsigned char>, ..., std::vector<double>, etc. Think about 200 types.

I would like to construct the Dynamic object very easily. If the type T is supported, the following code should work.

void f(const T& x) {
  il::Dynamic d = x;
  ...
}

It could be implemented very easily with the following constructor:

template <typename T>
Dynamic(const T& x) {
  ...
}

except that I have the following problem. On 64-bit platforms

const il::Dynamic d0 = 1'000'000'000;

would create an Dynamic containing an int, and

const il::Dynamic d1 = 10'000'000'000;

would create something else (don't even want to know if it is a long, or a long long). I don't want that, and the only integer type I want to use above 32 bits is a signed integer of the same size as std::size_t which is std::ptrdiff_t. As a consequence, I want to prevent the constructor to work with the following types : int, unsigned int, long, unsigned long, long long, unsigned long long, except one which is std::ptrdiff_t. How could I do such as thing?

InsideLoop
  • 6,063
  • 2
  • 28
  • 55
  • Aside from the question itself, is there any reason you don't use `std::any` or `std::variant`? – lisyarus Jan 24 '18 at 10:47
  • 1
    Search "ad hoc polymorphism." If something like that is really desirable, which it almost never is, there are better ways. Maybe virtual inheritance, or RTTI, or std::any, or std::variant, or one of the other things I am not thinking of at the moment. – Jive Dadson Jan 24 '18 at 12:15
  • @lisyarus: This is part of a library, and most of my clients compile without RTTI. Moreover, I want the best performance, and rolling out your own implementation allows you many tricks such as having a `Dynamic` types of 64 bits ans storing in place types such as integers and doubles. – InsideLoop Jan 24 '18 at 13:17
  • 1
    @InsideLoop Then, `std::variant` should suit you: it doesn't use RTTI, and takes up the minimum required amount of space, while being much more type-safe. – lisyarus Jan 24 '18 at 13:19
  • @lisyarus: There are more than 100 types, so the `std::variant` would have a lot of template parameters. But that could be ok. Except that in the end, I want my Dynamic object to use only 8 bytes. The reason for that is this `Dynamic` type is usually stored as the value of an hash table where the key is a string that takes 24 bytes. That way, I can put 2 (key, value) pairs in one cacheline which makes think really fast. And I am limited to `C++11`. – InsideLoop Jan 24 '18 at 13:25

3 Answers3

2

Specialize it and mark it as delete explicitly:

Dynamic(int) = delete;
Dynamic(unsigned int) = delete;
Dynamic(long) = delete;
Dynamic(unsigned long) = delete;
...

Repeat this for all types you don't want. Then when you attempt to call it you get a compile error.

If you want the reverse thing, i.e. allow the constructor for some types while blocking everything else, you can also do the reverse:

template <typename T>
Dynamic(const T& x) = delete;

Dynamic(char x) { /* Your code */ };
Dynamic(short x) { /* Your code */ };
Dynamic(std::size_t x) { /* Your code */ };
...

In this way only the types that you explicitly implemented will be allowed, as all other types will be matched by the template and rejected (with a compile error).

iBug
  • 35,554
  • 7
  • 89
  • 134
  • Thanks, but my problem is only half solved. Among `int`, `long` and `long long`, I want to delete all but `std::ptrdiff_t` which I believe could be `long` or `long long` depending upon the platform (even on 64-bit CPUs). For the time being, I use your second solution that works but is really painful (I have hundreds of types). – InsideLoop Jan 24 '18 at 13:12
2

You might delete the unwanted methods (via overloads):

class Dynamic {
public:
    template <typename T>
    Dynamic(const T& x) {
        // Your code...
    }

    Dynamic(int) = delete;
    Dynamic(unsigned int) = delete;
    Dynamic(long) = delete;
    // ...
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

You can use std::enable_if to restrict the use of a template function.

In the following code, is_allowed by default allows a type to be used in your constructor. Then you specialize is_allowed for those types you don't want to make is_allowed inherit from std::false_type, thus prevent them from being used in your constructor.

#include <type_traits>

template<class T>
struct is_allowed : std::true_type {};

template<>
struct is_allowed<int> : std::false_type {};

template<>
struct is_allowed<unsigned int> : std::false_type {};

/* ... */

class Dynamic {
public:
    template<class T,
        std::enable_if_t<is_allowed<T>::value, bool> = true>
    Dynamic(const T& x) { /* ... */ }
};

UPDATE:

Since OP says the size of std::ptrdiff_t is not known. So all types that should be removed cannot be listed manually. I assume from OP's info that the condition to accept a type is

!std::is_integral_v<T> || std::is_same_v<T, std::ptrdiff_t>

For other conditions, the solution is similar. Here is the code:

#include <type_traits>

template<class T>
struct is_allowed {
    constexpr static bool value = !std::is_integral_v<T> || std::is_same_v<T, std::ptrdiff_t>;
};

class Dynamic {
public:
    template<class T,
        std::enable_if_t<is_allowed<T>::value, bool> = true>
    Dynamic(const T& x) { /* ... */ }
};

This works for C++17. It's also ok for C++11/C++14, just replace that _v by ::value and _t by ::type respectively.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
  • 1
    `std::is_base_of_v>` can be replaced by `is_allowed::value` (and you can even add `_v` version if you want). – Jarod42 Jan 24 '18 at 14:04
  • 1
    Prefer `std::enable_if_t = false` over `typename = std::enable_if_t`. see [default-template-argument-when-using-stdenable](https://stackoverflow.com/questions/38305222/default-template-argument-when-using-stdenable-if-as-templ-param-why-ok-wit). – Jarod42 Jan 24 '18 at 14:22
  • @Jarod42 Nice point in that answer. But maybe using `std::enable_if_t = true` "looks" more nature. – llllllllll Jan 24 '18 at 15:35
  • or `std::enable_if_t = nullptr`. You got my point. – Jarod42 Jan 24 '18 at 16:15