Есть ли что-нибудь похожее на шаблонизированный case-statement

Итак, у меня есть этот действительно уродливый код:

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);}

Я использую вложенные conditional_t для создания своего рода case-statement. Есть ли что-то, что делает это более элегантно, или мне нужно приготовить собственное шаблонное выражение case?

Примечание. Я действительно знаю, что такое использование reinterpret_cast плохо: Почему reinterpret_cast Force copy_n не выполняет принудительное приведение типов между типами одинакового размера?


person Jonathan Mee    schedule 24.02.2015    source источник
comment
Что этот код пытается достичь?   -  person Slava    schedule 24.02.2015
comment
Вы можете сделать один шаблон для char, short, int, long long...   -  person amchacon    schedule 24.02.2015
comment
Почему это похоже на языки программирования с зависимой типизацией?   -  person Marcus Müller    schedule 24.02.2015
comment
@amchacon Вы предлагаете специализированные шаблоны? Потому что это то, чего я надеялся избежать.   -  person Jonathan Mee    schedule 24.02.2015
comment
@Слава Лол, я пытался найти способ очистить это: stackoverflow.com/a/28634468/2642059   -  person Jonathan Mee    schedule 24.02.2015
comment
@Slava: Преобразуйте объект любого типа в фундаментальный тип того же размера (если он существует), содержащий те же значения байтов.   -  person Mike Seymour    schedule 24.02.2015


Ответы (5)


Однажды мне пришлось сделать что-то подобное, поэтому я написал небольшую обертку, чтобы аккуратно получить результат. Вы можете использовать его следующим образом (см. здесь для теста)

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){ ... }

За кулисами он в значительной степени делает то, что у вас уже есть, но, обертывая его, мы сохраняем его (более) читабельным. Существует также версия, которая позволяет вам напрямую переключаться на тип T, если вам это нужно.

Изменить: по предложению @Deduplicator вот код, стоящий за ним.

#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!");
}; 
person Dan    schedule 24.02.2015
comment
Возможно, вы захотите импортировать свой код из github здесь ... это сделает ответ полным. Во всяком случае, я только что написал что-то подобное, но вы поняли. - person Deduplicator; 24.02.2015
comment
@ Дэн Я не уверен, что понимаю, как это применить. Если вы можете подключить его, проверить его будет довольно просто: auto val = foo(13.0);static_assert(is_same<long long, decltype(val)>::value); - person Jonathan Mee; 24.02.2015
comment
@JonathanMee: Кроме того, это можно упростить, если значение может быть получено из типа, как в вашем случае. - person Deduplicator; 24.02.2015
comment
@Deduplicator Итак, я только что разобрался, как работает static_switch. Что значит, можно упростить? - person Jonathan Mee; 24.02.2015
comment
@JonathanMee: Достаточно просто указать целевой размер и список типов, когда должен быть возвращен первый соответствующий тип. - person Deduplicator; 24.02.2015
comment
@Dan Возможно ли, чтобы это не сработало по умолчанию? Или мне нужно переписать специализацию, чтобы добиться этого? - person Jonathan Mee; 25.02.2015
comment
@JonathanMee Нет необходимости переписывать специализацию, но к ней можно добавить static_assert, я добавил ее в приведенный выше пример кода, и вы можете попробуйте здесь. - person Dan; 25.02.2015
comment
@Dan Я думаю, что это может быть лучшим решением для отказа от случая по умолчанию, потому что он оставляет нетронутой функциональность для определения значения по умолчанию, если Я так хочу по дороге. - person Jonathan Mee; 27.02.2015
comment
@JonathanMee Хм, это тоже возможно, но ошибка / код компилятора может быть непонятна третьей стороне, читающей любой из них. Делая это по-моему, вы все равно можете использовать случай по умолчанию, если хотите, но если вы передадите fail_on_default, он напечатает сообщение из static_assert, если произойдет сбой. Если только я не понял, что вы имеете в виду. - person Dan; 27.02.2015
comment
@ Дэн, я вижу точку ясности. Но тогда, если читатель понимает достаточно, чтобы знать, что делает ваш static_switch, я предполагаю, что он также поймет, что делает enable_if<false>. - person Jonathan Mee; 27.02.2015

Шаблонная версия оператора switch является специализированным шаблоном.

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);
}
person Raymond Chen    schedule 24.02.2015
comment
Обратите внимание, что на некоторых системах sizeof(int) может быть равно sizeof(long) и этот код не будет компилироваться. - person borisbn; 24.02.2015
comment
Верно, но у операторов switch та же проблема! - person Raymond Chen; 24.02.2015
comment
@borisbn Это также правда, что это приведет к ошибке времени компиляции, с которой, надеюсь, я достаточно умен, чтобы справиться? - person Jonathan Mee; 24.02.2015
comment
@Deduplicator: не было бы более эффективно прокомментировать вопрос, вместо того, чтобы рассылать спамом комментарии к ответам, которые касались вопроса в том виде, в котором он был задан? - person IInspectable; 07.06.2015
comment
@IInspectable: вопрос не пострадал от этой проблемы, только два решения, которые я прокомментировал. - person Deduplicator; 07.06.2015

Пока вы понимаете риск того, что тип того же размера может не быть конвертируемым, вы можете просто добавить 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");
    }    
}
person Nim    schedule 24.02.2015
comment
Это выглядит многообещающим решением. Но у меня нет Буста. Интересно, можно ли это как-то сделать с помощью std::map? - person Jonathan Mee; 24.02.2015
comment
@JonathanMee, это невозможно с std::map, вы можете сделать это с switch, но это будет тест времени выполнения, а не время компиляции ... - person Nim; 25.02.2015

Что-то вроде этого, возможно:

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);
person Igor Tandetnik    schedule 24.02.2015

Тег типа:

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

void_t (приходит в С++ 17 к компилятору рядом с вами):

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

enable_first_t берет пачку std::enable_if (обратите внимание на отсутствие _t) и возвращает первую, прошедшую тест. Вы можете использовать tag<X> вместо 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
>;

это во многом похоже на switch, но операторы представляют собой полные логические выражения.

живой пример

person Yakk - Adam Nevraumont    schedule 24.02.2015
comment
Почему не template<class...> using void_t = void;? - person dyp; 25.02.2015