自动识别合适的类型,足够大且足够精确,以保存容器中所有元素的总和

Automatically identify a suitable type, large enough and precise enough, to hold the sum of all elements in a container

(这个问题已经从原来的问题中进行了戏剧性的编辑,没有改变原始问题的真正意图)

如果我们将 vector<int> 中的所有元素加起来,那么答案可能会溢出,需要类似 intmax_t 的东西来 准确地存储答案 并且没有溢出。但是 intmax_t 不适合 vector<double>.

我可以手动指定类型:

template<typename>
struct sum_traits;

template<>
struct sum_traits<int> {
    typedef long accumulate_safely_t;
};

然后按如下方式使用它们:

template<typename C>
auto sum(const C& c) {
    sum_traits<decltype(c.begin())> :: accumulate_safely_t> r = 0;
    for(auto &el: c)
        r += el;
    return r;
}

我的问题:是否可以自动识别一个合适的类型,一个大而准确的类型,这样我就不必通过类型特征手动指定每个?

您的代码的主要问题是 auto r = 0 等同于 int r = 0。那不是你的 C++98 代码的工作方式。一般来说,你找不到完美的目标类型。您的代码只是 std::accumulate 的一个变体,所以我们可以看看标准是如何解决这个问题的:它允许您传入累加器的初始值,以及它的类型:long sum = std::accumulate(begin, end, long{0});

您可以像这样在 C++14 中使用 return type deduction

template<typename C>
auto sum(const C& c) {
    auto r = 0;
    for(auto &el: c)
        r += el;
    return r;
}

在 C++11 中,考虑到您的 C++98 代码,您可以使用以下内容:

template<typename C>
auto sum(const C& c) -> typename C::value_type {
    auto r = 0;
    for(auto &el: c)
        r += el;
    return r;
}

但是,正如评论中指出的那样,auto r = 0; 仍将在编译时解析为 int。正如在另一个答案中所建议的,您可能希望将初始值类型(以及 return 值类型)也设为模板参数:

template<typename C, typename T>
T sum(const C& c, T init) {
    for(auto &el: c)
        init += el;
    return init;
}

// usage

std::vector<std::string> v({"Hello ", "World ", "!!!"});
std::cout << sum(v, std::string{});

给定:

If we add up all the elements in a vector, then the answer could overflow, requiring something like intmax_t to store the answer accurately and without overflow.

问题:

My questions: Is it possible to automatically identify a suitable type, a large and accurate type, so I don't have to manually specify each one via the type trait?

这里的问题是你想获取运行时数据(一个向量)并从中推导出一个类型(一个编译时的东西)。

由于类型推导是一种编译时操作,我们必须仅使用编译时可用的信息来做出此决定。

我们在编译时唯一的信息(除非您提供更多)是 std::numeric_limits<int>::max()std::numeric_limits<std::vector<int>::size_type>::max().

在这个阶段你甚至没有 std::vector<int>::max_size(),因为它不是强制性的 constexpr。你也不能依赖 std::vector<int>::allocator_type::max_size() 因为它是:

  1. 一个成员函数
  2. 可选
  3. 在 c++17 中已弃用

所以我们剩下的是最大可能的总和:

std::numeric_limits<int>::max() * std::numeric_limits<std::vector<int>::size_type>::max()

我们现在可以使用编译时析取来找到合适的整数(如果存在这样的整数)(涉及 std::conditional

这不会使类型适应运行时条件,但它至少会适应您正在编译的体系结构。

像这样:

template <bool Signed, unsigned long long NofBits>
struct smallest_integer
{
    template<std::size_t Bits, class...Candidates>
    struct select_candidate;

    template<std::size_t Bits, class...Candidates>
    using select_candidate_t = typename select_candidate<Bits, Candidates...>::type;

    template<std::size_t Bits, class Candidate, class...Rest>
    struct select_candidate<Bits, Candidate, Rest...>
    {
        using type = std::conditional_t<std::numeric_limits<Candidate>::digits >= Bits, Candidate, select_candidate_t<Bits, Rest...>>;
    };

    template<std::size_t Bits, class Candidate>
    struct select_candidate<Bits, Candidate>
    {
        using type = std::conditional_t<std::numeric_limits<Candidate>::digits >= Bits, Candidate, void>;
    };

    using type =
    std::conditional_t<Signed,
    select_candidate_t<NofBits, std::int8_t, std::int16_t, std::int32_t, std::int64_t, __int128_t>,
    select_candidate_t<NofBits, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t, __uint128_t>>;
};

template<bool Signed, unsigned long long NofBits> using smallest_integer_t = typename smallest_integer<Signed, NofBits>::type;

template<class L, class R>
struct result_of_multiply
{
    static constexpr auto lbits = std::numeric_limits<L>::digits;
    static constexpr auto rbits = std::numeric_limits<R>::digits;
    static constexpr auto is_signed = std::numeric_limits<L>::is_signed or std::numeric_limits<R>::is_signed;
    static constexpr auto result_bits = lbits + rbits;

    using type = smallest_integer_t<is_signed, result_bits>;
};

template<class L, class R> using result_of_multiply_t = typename result_of_multiply<L, R>::type;

struct safe_multiply
{
    template<class L, class R>
    auto operator()(L const& l, R const& r) const -> result_of_multiply_t<L, R>
    {
        return result_of_multiply_t<L, R>(l) * result_of_multiply_t<L, R>(r);
    }
};

template<class T>
auto accumulate_values(const std::vector<T>& v)
{
    using result_type = result_of_multiply_t<T, decltype(std::declval<std::vector<T>>().max_size())>;
    return std::accumulate(v.begin(), v.end(), result_type(0), std::plus<>());
}

struct uint128_t_printer
{

    std::ostream& operator()(std::ostream& os) const
    {
        auto n = n_;
        if (n == 0)  return os << '0';

        char str[40] = {0}; // log10(1 << 128) + '[=10=]'
        char *s = str + sizeof(str) - 1; // start at the end
        while (n != 0) {

            *--s = "0123456789"[n % 10]; // save last digit
            n /= 10;                     // drop it
        }
        return os << s;
    }
    __uint128_t n_;
};
std::ostream& operator<<(std::ostream& os, const uint128_t_printer& p)
{
    return p(os);
}

auto output(__uint128_t n)
{
    return uint128_t_printer{n};
}

int main()
{
    using rtype = result_of_multiply<std::size_t, unsigned>;
    std::cout << rtype::is_signed << std::endl;
    std::cout << rtype::lbits << std::endl;
    std::cout << rtype::rbits << std::endl;
    std::cout << rtype::result_bits << std::endl;
    std::cout << std::numeric_limits<rtype::type>::digits << std::endl;

    std::vector<int> v { 1, 2, 3, 4, 5, 6 };
    auto z = accumulate_values(v);
    std::cout << output(z) << std::endl;


    auto i = safe_multiply()(std::numeric_limits<unsigned>::max(), std::numeric_limits<unsigned>::max());
    std::cout << i << std::endl;
}