17

I am currently doing this trick to have a cstring based on a type:

template<class ListT> static char constexpr * GetNameOfList(void)
{
  return
  std::conditional<
    std::is_same<ListT, LicencesList>::value, "licences",
    std::conditional<
      std::is_same<ListT, BundlesList>::value, "bundles",
      std::conditional<
        std::is_same<ListT, ProductsList>::value, "products",
        std::conditional<
          std::is_same<ListT, UsersList>::value, "users",
          nullptr
        >
      >
    >
  >;
}

But this code is not very good-looking, and if we want to check more types, this could be unreadable. Is it a way to do the same thing as if there were a switch case block?

Actually, the code is more complicated than that, because std::conditional need some type, so we need some class to do the trick:

struct LicenceName { static char constexpr * value = "licences"; };

for example.

Boiethios
  • 38,438
  • 19
  • 134
  • 183

3 Answers3

22

I think it would be easier using template specialization


Example code:

#include <iostream>
struct A{};
struct B{};
struct C{};
struct D{};

template<typename T> constexpr const char* GetNameOfList();
//here you may want to make it return nullptr by default

template<>constexpr const char* GetNameOfList<A>(){return "A";}
template<>constexpr const char* GetNameOfList<B>(){return "B";}
template<>constexpr const char* GetNameOfList<C>(){return "C";}

int main(){
   std::cout << GetNameOfList<A>() << '\n';
   std::cout << GetNameOfList<B>() << '\n';
   std::cout << GetNameOfList<C>() << '\n';
   //std::cout << GetNameOfList<D>() << '\n'; //compile error here
}
Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56
apple apple
  • 10,292
  • 2
  • 16
  • 36
  • Thank you, I like your solution. I did not think about template specialization; I not used to play with template. – Boiethios Nov 22 '16 at 16:13
  • 2
    Not just easier, but nicer, since an invalid argument ends up with a compiler error rather than a nullptr, or at least it is easy to make it do so – Justin Nov 22 '16 at 18:42
13

You don't need to resort to metaprogramming, plain ifs work just fine:

template<class ListT>
constexpr char const *GetNameOfList() {

    if(std::is_same<ListT, A>::value) return "A";
    if(std::is_same<ListT, B>::value) return "B";
    if(std::is_same<ListT, C>::value) return "C";
    if(std::is_same<ListT, D>::value) return "D";

    return nullptr;
}

See it live on Coliru

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 1
    Visual C++ says that **'constexpr' functions can only have one return statement** – Boiethios Nov 22 '16 at 16:20
  • @Boiethios which version of VC++ ? – Quentin Nov 22 '16 at 16:24
  • VC++ 2015 update 3 (and I cannot change the compiler options) – Boiethios Nov 22 '16 at 16:25
  • 1
    @Boiethios it looks like MSVC still hasn't implemented it, even in the latest version. If you're stuck with that compiler, it sounds like you'll have to use the template specialization solution until they update. – Quentin Nov 22 '16 at 16:28
  • It's a pity, I prefer your solution, but it seems VC++ is always late about new C++ stuff. I personally prefer g++ or clang++, but I have no choice at work. – Boiethios Nov 22 '16 at 16:30
  • 5
    @Boiethios you have my sympathy. I'm stuck with VC++ 2008 here ;) – Quentin Nov 22 '16 at 16:31
  • 3
    This answer can be trivially rewritten to a single `return` statement by using the `?:` operator. –  Nov 22 '16 at 18:01
  • 9
    @hvd: Okay but then you're basically back to the nested mess of the original. – Lightness Races in Orbit Nov 22 '16 at 18:46
  • I'm not sure if all MSVC 2015 Update 3 builds support variables in `constexpr` functions, but if they do, it's easy enough to [rewrite the function to have a single return value](http://pastebin.com/Us3Zk37t). I know the [online version](http://webcompiler.cloudapp.net/) supports this, but I'm not sure if they pushed it out to Update 3 as a whole. – Justin Time - Reinstate Monica Nov 22 '16 at 19:18
  • @LightnessRacesinOrbit No nested mess needed. `a ? b : c ? d : e ? f : g` can be nicely formatted where each condition and return value are put on a line, the result is easy to read and easy to maintain when conditions get added or removed. –  Nov 22 '16 at 22:42
  • 2
    @hvd: That is nested – Lightness Races in Orbit Nov 22 '16 at 22:54
  • @LightnessRacesinOrbit You called it a nested mess. I can agree that the OP's nesting in the question made the code messy. I don't agree that it would in my suggestion. –  Nov 22 '16 at 23:05
  • @hvd: Then we can agree to disagree =) IME there's little worse that a `a ? b : c ? d : e ? f : g` nightmare – Lightness Races in Orbit Nov 22 '16 at 23:09
3

You could create constexpr array of strings plus tuple of list types to create mapping list type -> index -> name (if you need the mapping index -> types containing strings just use tuple instead of array). c++17 approach could look as follows:

#include <type_traits>
#include <tuple>
#include <utility>
#include <iostream>

struct LicencesList{};
struct BundlesList{};
struct ProductsList{};
struct UsersList{};

using ListTypes = std::tuple<LicencesList, BundlesList, ProductsList, UsersList>;
constexpr const char *NameList[] = {"licences", "bundles", "products", "users"};

template <class Tup, class, class = std::make_index_sequence<std::tuple_size<Tup>::value>>
struct index_of;

template <class Tup, class T, std::size_t... Is>
struct index_of<Tup, T, std::index_sequence<Is...>> {
    static constexpr std::size_t value = ((std::is_same<std::tuple_element_t<Is, Tup>, T>::value * Is) + ...);
};


template<class ListT> static const char constexpr * GetNameOfList(void) {
  return NameList[index_of<ListTypes, ListT>::value];
}

int main() {
    constexpr const char *value = GetNameOfList<BundlesList>();
    std::cout << value << std::endl;
}

[live demo]

If you want to maintain c++11 compatibility the approach would be just a little bit longer (I used here Casey's answer to implement index_of structure):

#include <type_traits>
#include <tuple>
#include <iostream>

struct LicencesList{};
struct BundlesList{};
struct ProductsList{};
struct UsersList{};

using ListTypes = std::tuple<LicencesList, BundlesList, ProductsList, UsersList>;
constexpr const char *NameList[] = {"licences", "bundles", "products", "users"};

template <class Tuple, class T>
struct index_of;

template <class T, class... Types>
struct index_of<std::tuple<T, Types...>, T> {
    static const std::size_t value = 0;
};

template <class T, class U, class... Types>
struct index_of<std::tuple<U, Types...>, T> {
    static const std::size_t value = 1 + index_of<std::tuple<Types...>, T>::value;
};


template<class ListT> static const char constexpr * GetNameOfList(void) {
  return NameList[index_of<ListTypes, ListT>::value];
}

int main() {
    constexpr const char *value = GetNameOfList<BundlesList>();
    std::cout << value << std::endl;
}

[live demo]

Community
  • 1
  • 1
W.F.
  • 13,888
  • 2
  • 34
  • 81