Obtain integer from collection of possible types at compile time?

Viewed 70

The following code does not compile, because I don't know if what I want to do is possible, but it does show what I would like. I build, at compile time (if possible!), a collection of types and integer values; this is then used at compile time in the assignment operator which looks at the type that has been passed and stores that the corresponding integer from the collection in the member variable type_:

struct MyStructure {
  MyStructure(char* d, int size) {}

  std::array<pair<TYPE, int>> map {
    { int, 1 },
    { char, 2 },
    { double, 4 },
    { std::string, 8 }
  };

  template <typename T>
  auto operator=(const T &arg) {
    // Depending on the type of 'arg', I want to write a certain value to the member variable 'type_'
  }
  int type_ = 0;
};

int main() {

  MyStructure myStruct;
  myStruct = 1;             // Should cause 1 to be stored in member 'type_ '
  myStruct = "Hello world"; // Should cause 8 to be stored in member 'type_'
}

I need to solve this in C++17 please; extra respect for anyone who additionally gives a C++20 solution as that would be a learning opportunity!

2 Answers

Here's a basic blueprint that, with some cosmetic tweaks, can be slotted into your MyStructure:

#include <string>
#include <iostream>

template<typename T> struct type_map;

template<>
struct type_map<int> {
    static constexpr int value=1;
};

template<>
struct type_map<char> {
    static constexpr int value=2;
};

template<>
struct type_map<double> {
    static constexpr int value=4;
};

template<>
struct type_map<std::string> {
    static constexpr int value=8;
};

template<typename T>
void some_function(const T &arg)
{
    std::cout << type_map<T>::value << std::endl;
}

int main()
{
    some_function(1);                   // Result: 1
    some_function('a');                 // Result: 2
    some_function(std::string{"42"});   // Result: 8
    return 0;
}

C++17 provides an alternative to Sam's answer using if constexpr construct.

#include <type_traits>
#include <string>

template<typename T>
constexpr auto type_to_int() // Return type deduced in each branch, to int here except for the last "invalid" branch
{
    if constexpr(std::is_same_v<T, int>)
    {
        return 1;
    }
    else if constexpr(std::is_same_v<T, char>)
    {
        return 2;
    }
    else if constexpr(std::is_same_v<T, double>)
    {
        return 3;
    }
    else if constexpr(std::is_same_v<T, std::string>)
    {
        return 4;    
    }
    else
    {
        // Always false.
        // See note [1] below.
        static_assert(sizeof(T) == 0, "Unsupported type.");
    }
}

/// Analogue to std::is_same_v, for std::is_same
template<typename T>
constexpr auto type_to_int_v = type_to_int<T>();

See it here on godbolt.

Note that depending on context, you may need to compare to T without const or volatile qualifiers, or without reference.

In that case, simply

template<typename T>
constexpr auto type_to_int()
{
    using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
    // Same as before but replacing 'T' by 'type'
}

[1] : This is a "trick" presented by Ruslan in his comment on this answer. Associated godbolt.

Related