2

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!

Wad
  • 1,454
  • 1
  • 16
  • 33
  • Firstly, `MyStructure::operator=()` generally needs to return `MyStructure &`. Its return type does not vary with the argument (which is the right hand side of the expression). Beyond that, you can achieve what you want with overloads (e.g. one overload for an `int`, one for a `char`, etc). You could do that with a template (and explicit specialisation) but that's pointless unless the "generic" (unspecialised) form is meaningful, which it won't be in your case (e.g. passing an argument of some type that isn't in your map has no meaning anyway - it's probably better if that doesn't compile). – Peter Jul 17 '22 at 02:51
  • You may want to look at boost::mpl::map. – n. m. could be an AI Jul 17 '22 at 03:28

2 Answers2

3

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;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • I was just writing an answer to do this same thing. Have a +1 – NathanOliver Jul 17 '22 at 02:51
  • Great minds think alike. – Sam Varshavchik Jul 17 '22 at 02:52
  • OK, that's nice, and I actually understand that! I am guessing it's not possible to code this without adding a new specialization for each possible type? Can you also clarify what the tweaks would be please? – Wad Jul 17 '22 at 02:54
  • Well, there is no material difference between adding a new specialization versus adding a new row to the hypothetical array map of types to integer values. Same exact thing. There's more typing involved, but that's C++ for you. Can you clarify what's not clear to you, about adapting this to your code? Instead of array, declare all the specializations, instead of outputing the `int` token to `std::cout`, your `MyStructure` saves it somewhere, instead of a standalone function, the lookup happens in `MyStructure::operator=`, etc... Just minor things like that, if something's unclear, explain. – Sam Varshavchik Jul 17 '22 at 03:02
  • 1
    @Wad If you want to make it easier, you can wrap the specialization in a macro so you can get a syntax like `ADD_MAP(my_type, 42);` – NathanOliver Jul 17 '22 at 03:05
  • OK, I see. Thanks both very much! – Wad Jul 17 '22 at 03:06
1

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.

Erel
  • 512
  • 1
  • 5
  • 14