11

So I have this really ugly code:

template <typename T>
std::conditional_t<sizeof(T) == sizeof(char),
                   char,
                   conditional_t<sizeof(T) == sizeof(short),
                                 short,
                                 conditional_t<sizeof(T) == sizeof(long),
                                               long,
                                               enable_if_t<sizeof(T) == sizeof(long long),
                                                           long long>>>> foo(T bar){return reinterpret_cast<decltype(foo(bar))>(bar);}

I'm using nested conditional_ts to make a case-statement of sorts. Is there something out there that accomplishes this more elegantly or do I need to cook up my own templatized-case-statement?

Note: I am actually aware that this use of reinterpret_cast is bad: Why Doesn't reinterpret_cast Force copy_n for Casts between Same-Sized Types?

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

5 Answers5

10

I had to do something like this once so I wrote a small wrapper to acheive the result neatly. You could use it as follows (see here for a test)

template<class T>
typename static_switch<sizeof(T)
            ,int // default case
            ,static_case<sizeof(char),char>
            ,static_case<sizeof(short),short>
            ,static_case<sizeof(long),long>
            >::type foo(T bar){ ... }

Behind the scenes it pretty much does what you already have but by wrapping it we keep it (more) readable. There is also a version to allow you to switch direclty on the type T if you needed that.

Edit: At @Deduplicator's suggestion here is the code behind it

#include <type_traits>  

/* 
 * Select a type based on the value of a compile-time constant such as a 
 * constexpr or #define using static_switch. 
 */ 

template<int I,class T> 
struct static_case { 
    static constexpr int value = I; 
    using type = T; 
}; 

template<int I, class DefaultType, class Case1, class... OtherCases> 
struct static_switch{ 
    using type = typename std::conditional< I==Case1::value ,  
                    typename Case1::type, 
                    typename static_switch<I,DefaultType,OtherCases...>::type 
                     >::type; 
}; 

struct fail_on_default {};

template<int I, class DefaultType, class LastCase> 
struct static_switch<I,DefaultType,LastCase> { 
    using type = typename std::conditional< I==LastCase::value ,  
                    typename LastCase::type, 
                    DefaultType 
                     >::type; 

    static_assert(!(std::is_same<type, fail_on_default>::value),
                  "Default case reached in static_switch!");
}; 
Dan
  • 12,857
  • 7
  • 40
  • 57
  • 3
    You might want to import your code from github here... that would make the answer complete. Anyway, I was just writing something like that up, but you got it. – Deduplicator Feb 24 '15 at 15:25
  • @Dan I'm not sure I understand how to apply this. If you can plug it in, it should be fairly simple to test: `auto val = foo(13.0);static_assert(is_same::value);` – Jonathan Mee Feb 24 '15 at 15:25
  • @JonathanMee: As an aside, it can be simplified if the value can be derived from the type, like in your case. – Deduplicator Feb 24 '15 at 15:29
  • @Deduplicator So I just sorted out how `static_switch` works. What do you mean, "It can be simplified?" – Jonathan Mee Feb 24 '15 at 15:38
  • @JonathanMee: Simply that just giving a target-size and a list of types is enough, when the first matching type should be returned. – Deduplicator Feb 24 '15 at 15:44
  • @Dan is it possible to have this fail at the default case? Or do I need to rewrite the specialization to accomplish that? – Jonathan Mee Feb 25 '15 at 02:37
  • @JonathanMee It's not necessary to rewrite the specialization but one can add a `static_assert` to it, I've added it to the code sample above and you can [test it here](http://ideone.com/68KwUq). – Dan Feb 25 '15 at 08:30
  • @Dan I think [this](http://stackoverflow.com/q/28724017/2642059) may be a better solution for failing the default case cause it leaves intact the functionality to define a default if I so desire down the road. – Jonathan Mee Feb 27 '15 at 11:36
  • @JonathanMee Hmm, that's also a possibility but the compiler error / code might not be clear to a third party reading either of them. By doing it my way you can still use the default case if you want but if you do pass `fail_on_default` it will print the message from the `static_assert` if it happens to fail. Unless I've not understood what you mean. – Dan Feb 27 '15 at 12:11
  • @Dan I can see the clarity point. But then if the reader understands enough to know what your `static_switch` does I assume that he would also understand what `enable_if` does. – Jonathan Mee Feb 27 '15 at 12:45
5

The template version of a switch statement is a specialized template.

template<size_t n> struct matching_type;
template<> struct matching_type<sizeof(char)> { typedef char type; };
template<> struct matching_type<sizeof(short)> { typedef short type; };
template<> struct matching_type<sizeof(int)> { typedef int type; };
template<> struct matching_type<sizeof(long)> { typedef long type; };
template<> struct matching_type<sizeof(long long)> { typedef long long type; };

template<typename T>
matching_type<sizeof(T)>::type foo(T bar)
{
    return reinterpret_cast<decltype(foo(bar))>(bar);
}
Raymond Chen
  • 44,448
  • 11
  • 96
  • 135
3

As long as you understand the risk that the same sized type may not be the convertible, you could simply plugin a mpl::map..

typedef map<
      pair<int_<sizeof(char)>, char>,
      pair<int_<sizeof(short)>, short>,
      pair<int_<sizeof(int)>, int>,
      pair<int_<sizeof(long long)>, long long>
    > m;

e.g.

#include <algorithm>
#include <iostream>

#include <boost/mpl/at.hpp>
#include <boost/mpl/map.hpp>

using namespace boost::mpl;

typedef map<
      pair<int_<sizeof(char)>, char>,
      pair<int_<sizeof(short)>, short>,
      pair<int_<sizeof(int)>, int>,
      pair<int_<sizeof(long long)>, long long>
    > m;

template <typename T>
typename at<m, int_<sizeof(T)>>::type foo(T bar)
{ return reinterpret_cast<decltype(foo(bar))>(bar); }


struct doh
{
    std::string a, b, c;
};

int main()
{
    {
      char c;
      static_assert(std::is_same<decltype(foo(c)), char>::value, "error");
    }
    {
      short c;
      static_assert(std::is_same<decltype(foo(c)), short>::value, "error");
    }
    {
      int c;
      static_assert(std::is_same<decltype(foo(c)), int>::value, "error");
    }
    {
      long long c;
      static_assert(std::is_same<decltype(foo(c)), long long>::value, "error");
    }
    {
      double c;
      static_assert(std::is_same<decltype(foo(c)), long long>::value, "error");
    }    
    {
      doh c;
      static_assert(std::is_same<decltype(foo(c)), void_>::value, "error");
    }    
}
Nim
  • 33,299
  • 2
  • 62
  • 101
  • This looks like a promising solution. But I don't have Boost. I wonder if this is something that could be accomplished with a `std::map` somehow? – Jonathan Mee Feb 24 '15 at 16:28
  • 1
    @JonathanMee, not possible with `std::map`, you could do it with a `switch`, but it would be a run-time test rather than a compile time... – Nim Feb 25 '15 at 08:09
2

Something like this perhaps:

template <size_t N> struct SuitablySized;

template<> struct SuitablySized<sizeof(char)> {
  typedef char type;
};
template<> struct SuitablySized<sizeof(short)> {
  typedef short type;
};
// Add more cases to taste

template <typename T>
typename SuitablySized<sizeof(T)>::type foo(T bar);
Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
1

A type tag:

template<class T>struct tag{using type=T;};

void_t (coming in C++17 to a compiler near you):

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

enable_first_t takes a pack of std::enable_if (note the lack of _t), and returns the first that passes the test. You can use tag<X> to replace std::enable_if<true, X>:

template<class T,class=void>struct has_type:std::false_type{};
template<class T>struct has_type<T, void_t<typename T::type>>:std::true_type{};

namespace details {
  template<class, class...Ts>
  struct enable_first {};
  template<class T0, class...Ts>
  struct enable_first<std::enable_if_t< !has_type<T0>{} >, T0, Ts... >:enable_first<void, Ts...> {};
  template<class T0, class...Ts>
  struct enable_first<std::enable_if_t<  has_type<T0>{} >, T0, Ts...>:T0 {};
}

template<class...Ts>using enable_first_t=typename details::enable_first<void, Ts...>::type;

template<class T>
using result = enable_first_t<
  std::enable_if<sizeof(T)==sizeof(char), char>,
  std::enable_if<sizeof(T)==sizeof(short), short>,
  std::enable_if<sizeof(T)==sizeof(long), long>,
  tag<int> // default
>;

this behaves a lot like a switch, but the statements are full boolean expressions.

live example

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524