是否有类似于模板化Case语句的东西?

11

我有一个非常丑陋的代码:

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语句的东西。是否有更加优雅的方法来实现这个功能,还是我需要自己编写模板化的case语句?
注:我实际上知道这种使用reinterpret_cast是不好的:为什么reinterpret_cast在相同大小类型之间强制进行copy_n转换?

1
这段代码试图实现什么功能? - Slava
你可以为char,short,int,long long等类型创建一个模板。 - amchacon
为什么这感觉像是你在使用依赖类型编程语言? - Marcus Müller
@amchacon,您是在建议专门化模板吗?因为这正是我希望避免的。 - Jonathan Mee
1
@Slava 哈哈,我正在尝试找到一种方法来清理这个:http://stackoverflow.com/a/28634468/2642059 - Jonathan Mee
2
@Slava:将任何类型的对象转换为相同大小的基本类型(如果存在)并包含相同字节值。 - Mike Seymour
5个回答

10
我曾经需要做类似的事情,所以我写了一个小包装器来整洁地实现结果。你可以按照以下方式使用它(在这里看到测试)。
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!");
}; 

3
你可能想从Github导入你的代码到这里...那样答案就更完整了。无论如何,我刚刚正在撰写类似的内容,但你已经解决了问题。 - Deduplicator
@JonathanMee:顺便说一句,如果该值可以根据类型推导出来,那么它可以简化。就像在你的情况下一样。 - Deduplicator
@JonathanMee:简单来说,只需要给出目标大小和类型列表,当第一个匹配的类型应该被返回。 - Deduplicator
@JonathanMee 不需要重写专门化,但可以在其中添加static_assert。我已将其添加到上面的代码示例中,你可以在此处测试 - Dan
@JonathanMee 嗯,那也是一种可能性,但编译器错误/代码对于第三方读者来说可能不太清晰。通过我的方式,您仍然可以使用默认情况,但如果您传递 fail_on_default,则会打印来自 static_assert 的消息(如果它失败)。除非我没有理解您的意思。 - Dan
显示剩余5条评论

5

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

请注意,在某些系统中,sizeof(int)可能等于sizeof(long),因此此代码将无法编译。 - borisbn
2
没错,但是 switch 语句也有同样的问题! - Raymond Chen
@borisbn 这也意味着会出现编译时错误,但希望我足够聪明以应对? - Jonathan Mee
@Deduplicator:评论问题不是更有效率吗?而不是在回答上垃圾评论,这些回答已经按照问题的要求进行了回答。 - IInspectable
@IInspectable:这个问题没有遇到那个问题,只是我评论的两个解决方案有点问题。 - Deduplicator

3
只要你了解同样大小的类型可能不可转换的风险,你可以简单地插入一个 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");
    }    
}

这看起来是一个很有前途的解决方案。但是我没有Boost库。我想知道是否可以通过某种方式使用std::map来实现? - Jonathan Mee
1
@JonathanMee,使用std::map不可能实现这个功能,你可以使用switch语句来实现,但这将是一个运行时测试而不是编译时... - Nim

2
也许可以这样表达:

类似于这样的内容:

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

1

一个类型标签:

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

"

void_t(即将在您附近的C++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,但语句是完整的布尔表达式。

实时示例


为什么不使用template<class...> using void_t = void;呢? - dyp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接