基于 C++ 中可能的最大输入范围推导 return 类型

derivation of return type based on max range of input possible in C++

我最近在一次 C++ 面试中被问到这个问题,我在那里 被要求改进下面的代码,当 将两个 int 的结果相加得到 long 和 return 需要相应地派生类型。

下面的代码失败了,因为基于 decltype() 的推导不够智能,无法根据输入值的实际范围进行识别,但类型和推导的 return 类型相同。因此,如果 Tint,我们可能需要一些元编程模板技术来将 return 类型派生为 long

这怎么能概括任何提示或线索?

我觉得decltype()对这里没有帮助。

#include<iostream>
#include<string>
#include<climits>

using namespace std;

template<typename T> auto adder(const T& i1, const T& i2) -> decltype(i1+i2)
{  
  return(i1+i2);
}

int main(int argc, char* argv[])
{
  cout << adder(INT_MAX-10, INT_MAX-3) << endl; // wrong.
  cout << adder<long>(INT_MAX-10, INT_MAX-3) << endl; // correct!!.
  return(0);   
}

Hence we need perhaps some metaprogramming template technique to derive the return type as long if T is int.

没那么简单。

如果 Tint,您不确定 long 是否足够。

标准只这么说

1) int (sizeof(int) * CHAR_BIT) 的位数是 至少 16

2) long (sizeof(long) * CHAR_BIT) 的位数是 至少 32

3) sizeof(int) <= sizeof(long)

因此,如果编译器用 sizeof(int) == sizeof(long) 管理 int,这是完全合法的并且

adder<long>(INT_MAX-10, INT_MAX-3);

不起作用,因为 long 可能不足以包含(没有溢出)两个 int 之间的总和。

我没有看到一个简单而优雅的解决方案。

我认为最好的是基于 C++11 引入了以下类型这一事实

1) std::int_least8_t, 至少8位的最小整数类型

2) std::int_least16_t, 至少16位的最小整数类型

3) std::int_least32_t, 至少32位的最小整数类型

4) std::int_least64_t, 至少64位的最小整数类型

C++11 还引入了 std::intmax_t 作为最大宽度整数类型。

所以我提出以下模板类型选择器

template <std::size_t N, typename = std::true_type>
struct typeFor;

/* in case std::intmax_t is bigger than 64 bits */
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool,
   (N > 64u) && (N <= sizeof(std::intmax_t)*CHAR_BIT)>>
 { using type = std::intmax_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 32u) && (N <= 64u)>>
 { using type = std::int_least64_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 16u) && (N <= 32u)>>
 { using type = std::int_least32_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 8u) && (N <= 16u)>>
 { using type = std::int_least16_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N <= 8u)>>
 { using type = std::int_least8_t; };

即,给定位数,定义相应的最小 "at least" 整数类型。

我还提出以下建议using

template <typename T>
using typeNext = typename typeFor<1u+sizeof(T)*CHAR_BIT>::type;

给定类型 T,检测肯定包含两个 T 值之和的最小整数类型(位数至少为位数的整数T 加一)。

所以你的 adder() 就变成了

template<typename T>
typeNext<T> adder (T const & i1, T const & i2)
 { return {typeNext<T>{i1} + i2}; }

观察到 returned 值不只是

   return i1 + i2;

否则你 return 类型正确但值错误:i1 + i2 计算为 T 值,因此你可能会溢出,然后将总和分配给 typeNext<T>变量。

为避免此问题,您必须使用两个值 (typeNext<T>{i1}) 之一初始化 typeNext<T> 临时变量,然后添加另一个 (typeNext<T>{i1} + i2) 以获得 typeNext<T> 值,最后是 return 计算值。这样,总和计算为 typeNext<T> 总和,并且您没有溢出。

下面是一个完整的编译示例

#include <cstdint>
#include <climits>
#include <iostream>
#include <type_traits>

template <std::size_t N, typename = std::true_type>
struct typeFor;

/* in case std::intmax_t is bigger than 64 bits */
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool,
   (N > 64u) && (N <= sizeof(std::intmax_t)*CHAR_BIT)>>
 { using type = std::intmax_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 32u) && (N <= 64u)>>
 { using type = std::int_least64_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 16u) && (N <= 32u)>>
 { using type = std::int_least32_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 8u) && (N <= 16u)>>
 { using type = std::int_least16_t; };

template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N <= 8u)>>
 { using type = std::int_least8_t; };

template <typename T>
using typeNext = typename typeFor<1u+sizeof(T)*CHAR_BIT>::type;

template<typename T>
typeNext<T> adder (T const & i1, T const & i2)
 { return {typeNext<T>{i1} + i2}; }

int main()
 {
   auto x = adder(INT_MAX-10, INT_MAX-3);

   std::cout << "int:  " << sizeof(int)*CHAR_BIT << std::endl;
   std::cout << "long: " << sizeof(long)*CHAR_BIT << std::endl;
   std::cout << "x:    " << sizeof(x)*CHAR_BIT << std::endl;

   std::cout << std::is_same<long, decltype(x)>::value << std::endl;
 }

在我的 Linux 64 位平台中,int 是 32 位,long 是 64 位,x 也是 longdecltype(x) 是同一类型。

但这对我的平台来说是正确的;没有什么可以保证 longdecltype(x) 永远相同。

还要注意尝试获取两个 std::intmax_t 的总和的类型

 std::intmax_t  y {};

 auto z = adder(y, y);

给出错误并且无法编译,因为没有为比 sizeof(std::intmax_t)*CHAR_BIT 更大的 N 定义 typeFor