Elijah Lee
Elijah Lee

Reputation: 39

Is it possible to collect all explicitly instantiated types of one specific template with C++ metaprogramming?

I want to implement some compile time evaluation involving taking all explicitly instantiated types of one specific template as input, just like this:

template<typename T>
struct Box{T content;};

struct ExplicitlyInstantiatedBoxTypesHolder {
    using types = SOME_MAGICAL_META_PRGRAMMING_CODE; // todo
};

template<typename...>
void printTypes();

template<>
void printTypes(){}

template<typename First, typename... Rest>
void printTypes(){
    cout << typeid(First).name() << ", ";
    printTypeNames<Rest...>();
}

int main(){
    Box<int> box1;
    Box<long> box2;

    printTypes<ExplicitlyInstantiatedBoxTypesHolder::types...>();
    // expected output:
    //      Box<int>, Box<long>
}

Upvotes: 1

Views: 136

Answers (1)

Amir Kirsh
Amir Kirsh

Reputation: 13752

If you want just to hold (and print) the type Names

You can register the types that are created into a static 'Regsitrar'.

Let's have a base for all Box types. This base would also manage the registration and printing:

class BoxBase {
private:
    static std::set<std::string>& types() {
        static std::set<std::string> types;
        return types;
    }
protected:
    template<typename T>
    static int registerType() {
        std::string name = boost::typeindex::type_id<T>().pretty_name();
        if(types().insert(name).second) {
            // for the need of the line below see:
            // https://stackoverflow.com/questions/59365123
            static std::ios_base::Init force_init;
            std::cout << "registering a box of type: " << name << std::endl;
        }
        return 0;
    }
public:
    static void print_types() {
        for(const auto& type: types()) {
            std::cout << type << ' ';
        }
        std::cout << std::endl;
    }
};

Now class Box has to make sure it registers itself per each type created:

template<typename T> class Box: public BoxBase {
    const static int foo_;
public:
    Box() {
        std::cout << "creating a box of type: ";
        std::cout << boost::typeindex::type_id<T>().pretty_name() << std::endl;
    }
    ~Box() {
        // we use foo_ here to avoid optimizing its initialization
        std::cout << foo_ << std::endl;
    }
    void doSomething() const {}
};    

template<typename T>
const int Box<T>::foo_ = BoxBase::registerType<T>();

Now all Box types would be recorded, regardless of the location they are created!

int main() {
    std::cout << "-------------------------------" << std::endl;
    std::cout << "box types: ";
    BoxBase::print_types();
    std::cout << "-------------------------------" << std::endl;

    Box<long> long_box;
    Box<int> int_box;

    long_box.doSomething();
    int_box.doSomething();

    Box<long> long_box2;

    Box<double> double_box;    
}

void bar() {
    Box<char> box;  
}

Output:

registering a box of type: long
registering a box of type: int
registering a box of type: double
registering a box of type: char
-------------------------------
box types: char double int long 
-------------------------------
creating a box of type: long
creating a box of type: int
creating a box of type: long
creating a box of type: double

Code: http://coliru.stacked-crooked.com/a/c69162c8da91e51e


If you actually want to hold the Types

You can allow the user of the template to declare ahead the types that may be used, then only these types would be allowed and you can print the list of these types. This is also not exactly what you look for but maybe can be of a help to the problem in question.

The idea is to have a factory template that would manage the allowed types.

BoxFactory

template<typename... Ts> 
struct BoxesFactory {
    template<typename T>
    static Box<T> create_box() {
        static_assert(TypeExists<T, Ts...>::value);
        return Box<T>{};
    }
    static constexpr void print_types() {
        TypesPrinter<Ts...>();
    }
};

main

int main() {
    BoxesFactory<int, long> factory;
    std::cout << "Supported boxes: " << std::endl;
    factory.print_types();

    auto long_box = factory.create_box<long>();
    auto int_box = factory.create_box<int>();
    // auto double_box = factory.create_box<double>(); // <= compilation error

    long_box.do_box_things();
    int_box.do_box_things();
}

The class Box itself can be declared inside the private part of BoxFactory if you want to avoid the creation of Boxes outside the factory, or alternatively you can require in the ctor of Box a private token parameter that only BoxFactory can pass.

TypeExists

template<typename Validate, typename T, typename... Ts>
struct TypeExists {
    constexpr static bool value = TypeExists<Validate, T>::value
                                  || TypeExists<Validate, Ts...>::value;
};

template<typename Validate, typename T>
struct TypeExists<Validate, T> {
    constexpr static bool value = std::is_same<Validate, T>::value;
};

TypesPrinter

template<typename T, typename... Ts>
struct TypesPrinter {
    constexpr TypesPrinter() {
        TypesPrinter<T>();
        TypesPrinter<Ts...>();
    }
};

template<typename T>
struct TypesPrinter<T> {
    constexpr TypesPrinter() {
        // std::cout << typeid(T).name() << ' ';
        std::cout << boost::typeindex::type_id<T>().pretty_name() << ' ';
    }
};

Code: http://coliru.stacked-crooked.com/a/42f67cc6ce95e5c5


Side note: there are other techniques for restricting the instantiation of a template to certain types. But those do not deal with 'bookeeping' of the types being actually in use.

Upvotes: 1

Related Questions