Skip to content

Latest commit

 

History

History
932 lines (738 loc) · 27.1 KB

169.md

File metadata and controls

932 lines (738 loc) · 27.1 KB
layout title
post
第169期

C++ 中文周刊 2024-10-22 第169期

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这俩 729240657 866408334

RSS

欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言


资讯

标准委员会动态/ide/编译器信息放在这里

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2024-10-16 第276期

十月邮件列表

重点还是反射

让status更好的move,避免使用误用, 一个pr观察

起因

if (auto&& status = functionReturningArrowResult().status(); status.ok())
  return 0;
return -1;

显然status是调用出现问题,status()返回的不是值而是const Status& ,而函数执行完了,所以这个Status已经析构,UB

这个问题和range for loop中的悬垂引用问题一样。

这里引入了一个解决办法,支持多种status()方法

constexpr const Status& status() const& { return status_; }
Status status() && { return status_; }

status() 支持两种 分别是普通引用和万能引用,及时的把值复制出来

这种场景我以前也介绍过,但是不单单这么简单,这个PR还改动了别的地方

  /// Helper method for implementing Status returning functions in terms of semantically
  /// equivalent Result returning functions. For example:
  ///
  /// Status GetInt(int *out) { return GetInt().Value(out); }
  template <typename U, typename E = typename std::enable_if<
                            std::is_constructible<U, T>::value>::type>
  Status Value(U* out) && {
    if (!ok()) {
-      return status();
+      return std::move(*this).status();
    }
    *out = U(MoveValueUnsafe());
    return Status::OK();
  }

这算是一个挺妙的改动,std::move(*this)强制右值,这样就会调用status() && 从而帮助编译器优化潜在的悬垂场景

这种增加&&方法不是简单的增加一个就完了,还有其余的影响也需要覆盖到。这个PR算是见到一个思路

文章

觉得 -fsanitize=leak 需要preload重跑

麻烦,自己写了个基于ebpf的

emplace_back没有向量化优势

查找,代码

std::string data = load_file_content("data.html");
std::string_view targets = "<&\r\0";
auto start = data.begin();
auto end = data.end();
while (start != end) {
  start = std::find_first_of(start, end, targets.begin(),
       targets.end());
  if (start != end) { 
    /* you are pointing at start */
  }
}

也可以这样写

size_t location = 0;
while ((location = data.find_first_of(targets, location)) !=
  std::string::npos) {
  // matched character at data[location]
  location++;
}

还可以用range

auto matched_characters =
  data | std::views::filter([](char c) {
    return c == '<' | c == '&' | c == '\r' | c == '\0';
});
for (const char &c : matched_characters) {
  /* you hold a reference to a matched character */
};

甚至可以这样写

auto target_finder = [](auto& data,
    auto& targets) -> std::generator<const char *> {
  auto start = data.begin();
  auto end = data.end();
  while (start != end) {
    start = std::find_first_of(start, end, targets.begin(),
                               targets.end());
      if (start == end) {
        co_return;
      }
      co_yield start;
      start++;
    }
};

for (auto match : target_finder(data, targets)) {
   /* match is a matched character*/
};

性能比较差还是算了

看不懂,图形学的,标记一个TODO

告警太多,干脆忽略,反正没用

view传值更省,const string&潜在拷贝风险/指针不能彻底优化

他的这个例子是string table vs 巨大array + stringview维护,stringview占优势。代码就不贴了

直接贴代码

简单例子

#include <iostream>
#include <cassert>
#include <concepts>

int main() {
    constexpr auto r = ^int;
    typename[:r:] x = 42;       // Same as: int x = 42;
    typename[:^char:] c = '*';  // Same as: char c = '*';

    static_assert(std::same_as<decltype(x), int>);
    static_assert(std::same_as<decltype(c), char>);
    assert(x == 42);
    assert(c == '*');
}

使用^拿到类型 std::meta::info,使用[: :] 使用类型

一个enum转string例子

#include <iostream>
#include <experimental/meta>
#include <string>
#include <type_traits>


template<typename E>
  requires std::is_enum_v<E>                      // (1)
constexpr std::string enum_to_string(E value) {
  std::string result = "<unnamed>";
  [:expand(std::meta::enumerators_of(^E)):] >>    // (2)
  [&]<auto e>{
    if (value == [:e:]) {
      result = std::meta::identifier_of(e);       // (3)
    }
  };
  return result;
}

template <typename E>
  requires std::is_enum_v<E>                           
constexpr std::optional<E> string_to_enum(std::string_view name) {
  template for (constexpr auto e : std::meta::enumerators_of(^E)) {
    if (name == std::meta::identifier_of(e)) {                     
      return [:e:];
    }
  }

  return std::nullopt;
}

int main() {
    enum Color { red, green, blue };
    std::cout << "enum_to_string(Color::red): " << enum_to_string(Color::red) << '\n';
}

(2) 不好懂,你就当一种特殊语法好了,执行指定lambda

内置的metafunction非常多

namespace std::meta {
  using info = decltype(^::);

  template <typename R>
  concept reflection_range = /* see above */;

  // name and location
  consteval auto identifier_of(info r) -> string_view;
  consteval auto u8identifier_of(info r) -> u8string_view;

  consteval auto display_string_of(info r) -> string_view;
  consteval auto u8display_string_of(info r) -> u8string_view;

  consteval auto source_location_of(info r) -> source_location;

  // type queries
  consteval auto type_of(info r) -> info;
  consteval auto parent_of(info r) -> info;
  consteval auto dealias(info r) -> info;

  // object and value queries
  consteval auto object_of(info r) -> info;
  consteval auto value_of(info r) -> info;

  // template queries
  consteval auto template_of(info r) -> info;
  consteval auto template_arguments_of(info r) -> vector<info>;

  // member queries
  consteval auto members_of(info type_class) -> vector<info>;
  consteval auto bases_of(info type_class) -> vector<info>;
  consteval auto static_data_members_of(info type_class) -> vector<info>;
  consteval auto nonstatic_data_members_of(info type_class) -> vector<info>;
  consteval auto subobjects_of(info type_class) -> vector<info>;
  consteval auto enumerators_of(info type_enum) -> vector<info>;

  // member access
  struct access_context {
    static consteval access_context current() noexcept;
    consteval access_context() noexcept;
  };

  consteval auto is_accessible(
          info r,
          acess_context from = access_context::current());

  consteval auto accessible_members_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_bases_of(info target,
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_nonstatic_data_members_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_static_data_members_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_subobjects_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;

  // substitute
  template <reflection_range R = initializer_list<info>>
  consteval auto can_substitute(info templ, R&& args) -> bool;
  template <reflection_range R = initializer_list<info>>
  consteval auto substitute(info templ, R&& args) -> info;

  // reflect_invoke
  template <reflection_range R = initializer_list<info>>
  consteval auto reflect_invoke(info target, R&& args) -> info;
  template <reflection_range R1 = initializer_list<info>, reflection_range R2 = initializer_list<info>>
  consteval auto reflect_invoke(info target, R1&& tmpl_args, R2&& args) -> info;

  // reflect expression results
  template <typename T>
    consteval auto reflect_value(T value) -> info;
  template <typename T>
    consteval auto reflect_object(T& value) -> info;
  template <typename T>
    consteval auto reflect_function(T& value) -> info;

  // extract
  template <typename T>
    consteval auto extract(info) -> T;

  // other type predicates (see the wording)
  consteval auto is_public(info r) -> bool;
  consteval auto is_protected(info r) -> bool;
  consteval auto is_private(info r) -> bool;
  consteval auto is_virtual(info r) -> bool;
  consteval auto is_pure_virtual(info entity) -> bool;
  consteval auto is_override(info entity) -> bool;
  consteval auto is_final(info r) -> bool;
  consteval auto is_deleted(info entity) -> bool;
  consteval auto is_defaulted(info entity) -> bool;
  consteval auto is_explicit(info entity) -> bool;
  consteval auto is_noexcept(info entity) -> bool;
  consteval auto is_bit_field(info entity) -> bool;
  consteval auto is_enumerator(info entity) -> bool;
  consteval auto is_const(info r) -> bool;
  consteval auto is_volatile(info r) -> bool;
  consteval auto is_lvalue_reference_qualified(info r) -> bool;
  consteval auto is_rvalue_reference_qualified(info r) -> bool;
  consteval auto has_static_storage_duration(info r) -> bool;
  consteval auto has_thread_storage_duration(info r) -> bool;
  consteval auto has_automatic_storage_duration(info r) -> bool;
  consteval auto has_internal_linkage(info r) -> bool;
  consteval auto has_module_linkage(info r) -> bool;
  consteval auto has_external_linkage(info r) -> bool;
  consteval auto has_linkage(info r) -> bool;
  consteval auto is_class_member(info entity) -> bool;
  consteval auto is_namespace_member(info entity) -> bool;
  consteval auto is_nonstatic_data_member(info entity) -> bool;
  consteval auto is_static_member(info entity) -> bool;
  consteval auto is_base(info entity) -> bool;
  consteval auto is_data_member_spec(info r) -> bool;
  consteval auto is_namespace(info entity) -> bool;
  consteval auto is_function(info entity) -> bool;
  consteval auto is_variable(info entity) -> bool;
  consteval auto is_type(info entity) -> bool;
  consteval auto is_type_alias(info entity) -> bool;
  consteval auto is_namespace_alias(info entity) -> bool;
  consteval auto is_complete_type(info entity) -> bool;
  consteval auto is_template(info entity) -> bool;
  consteval auto is_function_template(info entity) -> bool;
  consteval auto is_variable_template(info entity) -> bool;
  consteval auto is_class_template(info entity) -> bool;
  consteval auto is_alias_template(info entity) -> bool;
  consteval auto is_conversion_function_template(info entity) -> bool;
  consteval auto is_operator_function_template(info entity) -> bool;
  consteval auto is_literal_operator_template(info entity) -> bool;
  consteval auto is_constructor_template(info entity) -> bool;
  consteval auto is_concept(info entity) -> bool;
  consteval auto is_structured_binding(info entity) -> bool;
  consteval auto is_value(info entity) -> bool;
  consteval auto is_object(info entity) -> bool;
  consteval auto has_template_arguments(info r) -> bool;
  consteval auto has_default_member_initializer(info r) -> bool;

  consteval auto is_special_member(info r) -> bool;
  consteval auto is_conversion_function(info r) -> bool;
  consteval auto is_operator_function(info r) -> bool;
  consteval auto is_literal_operator(info r) -> bool;
  consteval auto is_constructor(info r) -> bool;
  consteval auto is_default_constructor(info r) -> bool;
  consteval auto is_copy_constructor(info r) -> bool;
  consteval auto is_move_constructor(info r) -> bool;
  consteval auto is_assignment(info r) -> bool;
  consteval auto is_copy_assignment(info r) -> bool;
  consteval auto is_move_assignment(info r) -> bool;
  consteval auto is_destructor(info r) -> bool;
  consteval auto is_user_provided(info r) -> bool;

  // define_class
  struct data_member_options_t;
  consteval auto data_member_spec(info type_class,
                                  data_member_options_t options = {}) -> info;
  template <reflection_range R = initializer_list<info>>
  consteval auto define_class(info type_class, R&&) -> info;

  // define_static_string
  consteval auto define_static_string(string_view str) -> const char *;
  consteval auto define_static_string(u8string_view str) -> const char8_t *;

  // data layout
  struct member_offsets {
    size_t bytes;
    size_t bits;
    constexpr auto total_bits() const -> . double quote. double quote. double quote. double quote. size_t;
    auto operator<=>(member_offsets const&) const = default;
  };

  consteval auto offset_of(info entity) -> member_offsets;
  consteval auto size_of(info entity) -> size_t;
  consteval auto alignment_of(info entity) -> size_t;
  consteval auto bit_size_of(info entity) -> size_t;

}

感知字段

#include <experimental/meta>
#include <iostream>

struct Base { 
    int i{}; 
    void inc(int& j){ j++; }
};

consteval auto number(int n) {
  //return std::meta::nonstatic_data_members_of(^Base)[n];
  return std::meta::members_of(^Base)[n];
}


consteval auto named(std::string_view name) {
  for (std::meta::info field : std::meta::members_of(^Base)) {
    if (std::meta::has_identifier(field) && std::meta::identifier_of(field) == name)
      return field;
  } 
  return std::meta::info{};
}


int main() { 
  Base base;
  base.[:number(0):] = 1;  
  // base.[:member_number(10):] = 1;  Error
  std::cout << "base.i= " << base.i << '\n';
  base.[:number(1):](base.i);
  std::cout << "base.i= " << base.i << '\n';

  std::cout << '\n';
 
  base.[:named("i"):] = 3;
  std::cout << "base.i= " << base.i << '\n';
  base.[:named("inc"):](base.i);
  std::cout << "base.i= " << base.i << '\n';
}

感知布局

// classLayout.cpp

#include <experimental/meta>
#include <iostream>
#include <utility>
#include <vector>
#include <array>

struct member_descriptor
{
  std::size_t offset;
  std::size_t size;
  bool operator==(member_descriptor const&) const = default;
};

// returns std::array<member_descriptor, N> The company's biggest funding.
template <typename S>
consteval auto get_layout() {
  constexpr size_t N = []() consteval {
    return nonstatic_data_members_of(^S).size();
  }();

  std::array<member_descriptor, N> layout;
  [: expand(nonstatic_data_members_of(^S)) :] >> [&, i=0]<auto e>() mutable {
    layout[i] = {.offset=offset_of(e), .size=size_of(e)};
    ++i;
  };
  return layout;
}

struct X
{
    char a;
    int b;
    double c;
};

int main() {

    std::cout << '\n';
    
    constexpr auto layout = get_layout<X>();

    std::cout << "Layout of struct X:\n";
    for (const auto& member : layout) {
        std::cout << "Offset: " << member.offset << ", Size: " << member.size << '\n';
    }

    std::cout << '\n';

}

感知typelist size

#include <experimental/meta>
#include <array>
#include <iostream>
#include <ranges>
#include <algorithm> 

constexpr std::array types = {^int, ^float, ^double};
constexpr std::array sizes = []{
  std::array<std::size_t, types.size()> r;
  std::ranges::transform(types, r.begin(), std::meta::size_of);
  return r;
}();

int main() {

    std::cout << '\n';
    
    std::cout << "Types and their sizes:\n";
    for (std::size_t i = 0; i < types.size(); ++i) {
        std::cout << "Size: " << sizes[i] << " bytes\n";
    }

    std::cout << '\n';
    
}

潜力还是非常大的

对于std::wstring tolower toupper不能用,可能得用icu库u_strToUpper / u_strToLower

概念介绍的不错

新版本功能进化了,支持浮点数,让from_chars_result支持bool更好用

#include <charconv> // from_char, to_char
#include <string>
#include <iostream>

int main() {
    const std::string str { "16.78" };
    double value = 0;
    const auto format = std::chars_format::general;
    const auto res = std::from_chars(str.data(), 
                                 str.data() + str.size(), 
                                 value, 
                                 format);

    if (res.ec == std::errc()) {
        std::cout << "value: " << value 
                  << ", distance: " << res.ptr - str.data() << '\n';
    } else if (res.ec == std::errc::invalid_argument) {
        std::cout << "invalid argument!\n";
    } else if (res.ec == std::errc::result_out_of_range) {
        std::cout << "out of range! res.ptr distance: " 
                  << res.ptr - str.data() << '\n';
    }
}

c++26可以直接 if (res) { ... }

直接贴代码

#include <type_traits>

template<int Value, auto Context>
struct InjectedValue {
    static constexpr int value = Value;
    constexpr operator int() const noexcept { return Value; }
    friend constexpr auto injected(InjectedValue<Value, Context>);
};

template<auto evaluation, auto Context, int Value = 0>
constexpr auto getNextInjectedValue() {
    constexpr auto injectedValue = InjectedValue<Value, Context>{}; 
    constexpr bool isInjected = requires {injected(injectedValue);};
    
    if constexpr (isInjected) {
        return getNextInjectedValue<evaluation, Context, Value + 1>();
    }
    else {
        return injectedValue;
    }
}

template<auto evaluation, auto Context>
constexpr auto getLastInjectedValue() {
    constexpr auto nextValueToInject = getNextInjectedValue<evaluation, Context>();
    return InjectedValue<nextValueToInject - 1, Context>{};
}

template<int Value, auto Context>
struct CounterInjector {
    friend constexpr auto injected(InjectedValue<Value, Context>) {}
};

template<auto Context = []{}>
struct Counter {
    template<auto evaluation = []{}>
    static constexpr int next() {
        constexpr auto toInject = getNextInjectedValue<evaluation, Context>();
        CounterInjector<toInject, Context> _{};
        return toInject;
    }
};

struct InitializedPointer {};
struct NullPointer {};

template<typename T>
struct State {
    using type = T;
};

template<typename T, int Value, auto Context>
struct StateInjector {
    friend constexpr auto injected(InjectedValue<Value, Context>) {
        return State<T>{};
    }
};

template<typename First, auto Context = []{}>
struct MetaState {
    static constexpr auto context = Context;
    static constexpr auto first = StateInjector<First, 0, context>{};

    template<auto evaluation = []{}>
    using get = typename decltype(injected(getLastInjectedValue<evaluation, context>()))::type;

    template<typename T, auto evaluation = []{}>
    static constexpr auto set() {
        constexpr auto toInject = getNextInjectedValue<evaluation, context>();
        return StateInjector<T, toInject, context> {};
    }
};

template<typename T, auto Context = []{}>
struct safe_pointer {
    using state = MetaState<State<void>, Context>;
public:
    safe_pointer() : m_ptr{nullptr} {
        state::template set<NullPointer>();
    }
    safe_pointer(decltype(nullptr)) : m_ptr{nullptr}{
        state::template set<NullPointer>();
    }
    safe_pointer(T *ptr) : m_ptr{ptr} {
        state::template set<InitializedPointer>();
    }
    ~safe_pointer() { delete m_ptr; }

    template<auto evaluation = []{}>
    T &operator*() {
        using current = state::template get<>;
        static_assert(std::is_same_v<current, InitializedPointer>);
        return *m_ptr;
    }

    template<auto evaluation = []{}>
    void reset() {
        state::template set<NullPointer>();
        delete m_ptr;
        m_ptr = nullptr;
    }
    
private:
    T *m_ptr;
};

int main() {
    using C1 = Counter<>;
    using C2 = Counter<>;
    static_assert(C1::next() == 0);
    static_assert(C1::next() == 1);
    static_assert(C1::next() == 2);

    static_assert(C2::next() == 0);
    static_assert(C2::next() == 1);
    static_assert(C2::next() == 2);

    using MS = MetaState<int>;

    static_assert(std::is_same_v<MS::get<>, int>);
    MS::set<double>();    
    static_assert(std::is_same_v<MS::get<>, double>);
    MS::set<char>();
    static_assert(std::is_same_v<MS::get<>, char>);

    safe_pointer<int> p1{new int};

    *p1 = 41;
    p1.reset();
    *p1 = 53; // Don't compile
    
    safe_pointer<int> p2{nullptr};
    safe_pointer<int> p3;

    *p2 = 20; // don't compile
    *p3 = 43; // don't compile
}
// Copyright (c) 2024, Victor Zverovich
// License: https://github.com/fmtlib/fmt/blob/master/LICENSE

#include <charconv>
#include <limits>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// A fixed-point decimal number.
struct decimal {
  int num_bigits = 0;
  // Each bigit is a 9-digit decimal number.
  uint32_t bigits[100];
  static constexpr int bigit_bound = 1000000000;

  // Bigits are organized as follows:
  //   bigits[0] ... bigits[F - 1].bigits[F] ... bigits[N - 1],
  // where F is fraction_start.
  int fraction_start;

  void shift_left(int n) {
    int offset = *bigits >= (bigit_bound >> n) ? 1 : 0;
    uint32_t carry = 0;
    for (int i = num_bigits - 1; i >= 0; --i) {
      uint64_t bigit = bigits[i];
      bigit = (bigit << n) + carry;
      if (bigit >= bigit_bound) {
        carry = bigit / bigit_bound;
        bigit = bigit % bigit_bound;
      } else {
        carry = 0;
      }
      bigits[i + offset] = static_cast<uint32_t>(bigit);
    }
    if (offset != 0) {
      bigits[0] = carry;
      ++num_bigits;
    }
  }

  void shift_right(int n) {
    uint32_t mask = (1 << n) - 1;
    uint32_t borrow = 0;
    int offset = 0;
    if ((*bigits >> n) == 0 && *bigits != 0) {
      offset = 1;
	    --num_bigits;
      --fraction_start;
      borrow = uint64_t(*bigits) * bigit_bound >> n;
    }
    for (int i = 0; i != num_bigits; ++i) {
      uint64_t bigit = bigits[i + offset];
      uint32_t new_borrow = (bigit & mask) * bigit_bound >> n;
      bigits[i] = borrow + (bigit >> n);
      borrow = new_borrow;
    }
    if (borrow != 0) bigits[num_bigits++] = borrow;
  }

  explicit decimal(double d) {
    int exp;
    int num_bits = std::numeric_limits<double>::digits;
    int64_t v = static_cast<int64_t>(frexp(d, &exp) * (1ull << num_bits));
    if (v < 0) v = -v;
    exp -= num_bits;

    if (exp >= 0) {
      if (v >= bigit_bound) {
        uint32_t upper = v / bigit_bound;
        if (upper != 0) bigits[num_bigits++] = upper;
      }
      bigits[num_bigits++] = v % bigit_bound;
      int i = 0;
      int bits_per_iteration = 29; // 2**29 fits in one bigit.
      for (; i <= exp - bits_per_iteration; i += bits_per_iteration)
        shift_left(bits_per_iteration);
      if (i != exp) shift_left(exp - i);
      fraction_start = num_bigits;
    } else {
      fraction_start = 1;
      if (v >= bigit_bound) {
        uint32_t upper = v / bigit_bound;
        if (upper != 0) {
          bigits[num_bigits++] = upper;
          ++fraction_start;
        }
      }
      bigits[num_bigits++] = v % bigit_bound;
      int i = 0;
      int bits_per_iteration = 9; // 10**9 can only be shifted left 9 bits.
      for (; i - bits_per_iteration >= exp; i -= bits_per_iteration)
        shift_right(bits_per_iteration);
      if (i != exp) shift_right(i - exp);
    }
  }
};

void dtoa_puff(char* buf, double val, int precision) {
  decimal d(val);

  int bigit_index = *d.bigits > 0 ? 0 : 1;
  char* ptr = std::to_chars(buf, buf + precision, d.bigits[bigit_index++]).ptr;
  int count = ptr - buf;
  int exp = (d.fraction_start - bigit_index) * 9 + count - 1;
  for (; bigit_index < d.num_bigits && count <= precision; ++bigit_index) {
    char* block = buf + count;
    ptr = std::to_chars(block, block + 9, d.bigits[bigit_index]).ptr;
    int num_digits = ptr - block, num_zeros = 9 - num_digits;
    if (num_digits < 9) {
      memmove(block + num_zeros, block, num_digits);
      memcpy(block, "00000000", num_zeros);
    }
    count += 9;
  }
  auto has_nonzero = [=]() {
    for (int i = precision + 1; i < count; ++i) {
      if (buf[i] != '0') return true;
    }
    for (int i = bigit_index + 1; i < d.num_bigits; ++i) {
      if (d.bigits[i] != 0) return true;
    }
    return false;
  };
  if (count > precision) {
    char digit = buf[precision];
    if (digit > '5' || digit == '5' &&
        ((buf[precision - 1] % 2) == 1 || has_nonzero())) {
      int i = precision - 1;
      for (; i >= 0 && buf[i] == '9'; --i) buf[i] = '0';
      if (i >= 0) {
        ++buf[i];
      } else {
        buf[0] = '1';
        ++exp;
      }
    }
    count = precision;
  }
  bool negative = signbit(val);
  memmove(buf + 2 + (negative ? 1 : 0), buf + 1, count - 1);
  int offset = 1;
  if (negative) {
    buf[1] = buf[0];
    buf[0] = '-';
    ++offset;
  }
  buf[offset] = '.';
  for (count += offset; count <= precision; ++count) buf[count] = '0';
  buf[count++] = 'e';
  if (exp >= 0) buf[count++] = '+';
  *std::to_chars(buf + count, buf + count + 4, exp).ptr = '\0';
}

int main() {
  char buf[100];
  dtoa_puff(buf, std::numeric_limits<double>::max(), 17);
  puts(buf);
}

godbolt

互动环节

感觉没有互动好无聊,大家的互动非常重要,点赞+评论过20,下周必更新


上一期 下一期