Closed23

競プロ C++ テンプレート思案

atreeatree

今のテンプレ

// template {{{
#include <bits/stdc++.h>
#define overload2(_NULL, _1, _2, name, ...) name
#define rep1(i, n) for (std::decay_t<decltype(n)> i = 0; i < (n); i++)
#define rep2(i, a, b) for (std::decay_t<decltype(b)> i = (a); i < (b); i++)
#define rep(...) overload2(__VA_ARGS__, rep2, rep1)(__VA_ARGS__)
#if __has_include(<debug.hpp>)
#    include <debug.hpp>
#else
#    define dbg(...) static_cast<void>(0)
#endif
template<class T> [[noreturn]] void drop(const T &x) {
    std::cout << x << "\n";
    exit(0);
}
template<class T, class U = T> bool chmin(T &a, U &&b) { return b < a ? a = std::forward<U>(b), true : false; }
template<class T, class U = T> bool chmax(T &a, U &&b) { return a < b ? a = std::forward<U>(b), true : false; }
using i64 = std::int64_t;
using usize = std::size_t;
template<class F> struct rec_lambda {
    F f;
    explicit constexpr rec_lambda(F &&f_): f(std::forward<F>(f_)) {}
    template<class... Args> constexpr auto operator()(Args &&...args) const {
        return f(*this, std::forward<Args>(args)...);
    }
};
__attribute__((constructor)) static void io_setup() noexcept {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout << std::fixed << std::setprecision(10);
    std::cerr << std::fixed << std::setprecision(10);
}
// }}}

using namespace std;

int main() {}

atreeatree
  • どうせ Langage Server は Clangd なので GCC から足を洗って完全に Clang ユーザーになったほうが楽とかあるかもしれない。(懸念事項: 入出力)
  • <bits/stdc++.h> じゃなくてたくさんヘッダを書き連ねる。
  • using namespace std 問題。ライブラリではつかってないが最近何となく気持ち悪くなってきたけど、少なくとも global を汚すのは気がひける気がしてきた。ref: https://kmyk.github.io/blog/blog/2020/10/25/coding-style-for-competitive-programming
  • C++20 を使いたいのでバイナリ提出も考えられる。-> C++ を使う理由に JOI, PCK などのためといった理由があるがルールに抵触しそうなので Rust を使うべき。
  • {{{ / }}} による fold がいいかんじなので長さとかは気にしない。
atreeatree

ほしい物リスト

  • rep macro or iterator utilities(std::ranges like なやつ)
  • zip, enumerate
  • type alias
  • chmax / chmin(そのままでよさげ)
  • 入出力の wrapper(read syscall を使ってフルスクラッチしてもよい?)
  • rec_lambda (そのままでよさげ 2)
  • make_vector
  • drop(なんやかんや ABC - A, B, C では便利)
  • <bit> に入ってる人々
  • constexpr 10^n(繰り返し二乗法をいれちゃう?)
  • io_setup
  • template <class T, T Div = 2> constexpr T INF = std::numeric_limits<T>::max() / Div
  • ユーザー定義 literal
  • sort_by_key
  • こういうのもアリ? CF みたいな前処理なしクエリに対応しやすそう。
#include <...>
namesapce atr {
using namespace std;
void main() { ... }
}
int main() { atr::main(); }
  • 地味だけどよさげ
#define LAMBDA(...) [&]([[maybe_unused]] const auto& _) { return __VA_ARGS__; }
#define LAMBDA2(...) [&](const auto& _1, const auto& _2) { return __VA_ARGS__; }
  • かなり挑戦的だけど argio みたいなのもアリ?
    • C++ にはユーザー定義 attribute はないけど↑みたいに main じゃないところにソースコードを書くようにすればアリかも。入力は厳しいかも。
atreeatree

戻るのは簡単なので基本は攻めていこうと思う。

コンパイラ依存の機能を使うか

よく考えれば別にbits/stdc++.hで GCC の犬になってもいいかな。

using namespace std;

まあmainの中だけかな。

#include <bits/stdc++.h>

namespace atr { void main() {}; }

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout << std::fixed << std::setprecision(10);
    std::cerr << std::fixed << std::setprecision(10);
    atr::main();
}

namespace atr {
void main() {
    using namespace std;
}
} // namespace atr
atreeatree

rep

とりあえず iterator っぽいものを使う

template<class T = size_t> struct rep {
    struct itr {
        T i, width = 1;
        constexpr itr(const T i, const T width = 1) noexcept: i(i), width(width) {}
        void operator++() noexcept { i += width; }
        constexpr T operator*() const noexcept { return i; }
        constexpr bool operator!=(const itr x) const noexcept { return i < x.i; }
    };
    const itr f, l;
    constexpr rep(const T f, const T l = std::numeric_limits<T>::max(), const T width = 1) noexcept
        : f(std::min(f, l), width), l(l, width) {}
    constexpr auto begin() const noexcept { return f; }
    constexpr auto end() const noexcept { return l; }
};
atreeatree
namespace atr {
using i32 = std::int32_t;
using u32 = std::uint32_t;
using i64 = std::int64_t;
using u64 = std::uint64_t;
using usize = std::size_t;

constexpr std::int32_t operator""_i32(unsigned long long n) noexcept { return static_cast<std::int32_t>(n); }
constexpr std::int64_t operator""_i64(unsigned long long n) noexcept { return static_cast<std::int64_t>(n); }
constexpr std::uint32_t operator""_u32(unsigned long long n) noexcept { return static_cast<std::uint32_t>(n); }
constexpr std::uint64_t operator""_u64(unsigned long long n) noexcept { return static_cast<std::uint64_t>(n); }
constexpr std::size_t operator""_z(unsigned long long n) noexcept { return static_cast<usize>(n); }

template<class T, T N = 2> inline constexpr auto INF = std::numeric_limits<T>::max() / N;

template<class T, class U = T> bool chmin(T& a, U&& b) { return b < a ? a = std::forward<U>(b), true : false; }
template<class T, class U = T> bool chmax(T& a, U&& b) { return a < b ? a = std::forward<U>(b), true : false; }
}
atreeatree

天啓が走ったので argio のやつができそう。
C++17 では std::tupleがいい感じに推論されるって話だったので

return { { a, b }, { c } };

a b
c

で出力されるみたいなのができるんじゃないのか。
問題は vector みたいなコンテナだけど。空白区切りか改行区切りかはかなり ad-hoc なイメージがあって、それをどうするか。(セパレータ文字を変数で持てばいい?)
あとは

return {n, v};

はさすがに

n
v_0 v_1 ... v_{n-1}

になってほしい(コンテナの前後は強制改行?)

atreeatree

Rust like に iterator を取り回すのはとりあえずなしかな、C++(おそらく <ranges> についての知識が不可欠、それ以外もないけど)が足りないし本当に使いたくなったら Rust になる。reprevrep だけで。
ref: https://marycore.jp/prog/cpp/pipe-and-tailing-closure
revrep の実装が意外と嫌な気持ちになるな、T が unsigned かつ width != 1 だと嬉しくない。一番丸いのは rep(f, l, w) | reversed みたいな魔法だけどそれは諦めたのでとりあえず w == 1 固定にするか。

atreeatree

暫定版

// template {{{
#include <bits/stdc++.h>

namespace atr {
// type aliases {{{
using i32 = std::int32_t;
using u32 = std::uint32_t;
using i64 = std::int64_t;
using u64 = std::uint64_t;
using usize = std::size_t;
// }}}

// user defined literals {{{
constexpr std::int32_t operator""_i32(unsigned long long n) noexcept { return static_cast<std::int32_t>(n); }
constexpr std::int64_t operator""_i64(unsigned long long n) noexcept { return static_cast<std::int64_t>(n); }
constexpr std::uint32_t operator""_u32(unsigned long long n) noexcept { return static_cast<std::uint32_t>(n); }
constexpr std::uint64_t operator""_u64(unsigned long long n) noexcept { return static_cast<std::uint64_t>(n); }
constexpr std::size_t operator""_z(unsigned long long n) noexcept { return static_cast<size_t>(n); }
// }}}

// make_vector {{{

/**
 * @brief 多次元 vector の作成
 * @author えびちゃん
 */

namespace detail {
template<typename Tp, size_t Nb> auto make_vector(std::vector<size_t>& sizes, Tp const& x) {
    if constexpr (Nb == 1) {
        return std::vector(sizes[0], x);
    } else {
        size_t size = sizes[Nb - 1];
        sizes.pop_back();
        return std::vector(size, make_vector<Tp, Nb - 1>(sizes, x));
    }
}
}  // namespace detail

template<typename Tp, size_t Nb> auto make_vector(size_t const (&sizes)[Nb], Tp const& x = Tp()) {
    std::vector<size_t> s(Nb);
    for (size_t i = 0; i < Nb; ++i) s[i] = sizes[Nb - i - 1];
    return detail::make_vector<Tp, Nb>(s, x);
}
// }}}

// recusive lambda {{{
template<class F> struct rec_lambda {
    F f;
    explicit constexpr rec_lambda(F&& f): f(std::forward<F>(f)) {}
    template<class... Args> constexpr auto operator()(Args&&... args) const { return f(*this, std::forward<Args>(args)...); }
};
// }}}

// math {{{
template<class T, T N = 2> inline constexpr auto INF = std::numeric_limits<T>::max() / N;

template<class T> constexpr std::enable_if_t<std::is_arithmetic_v<T>, T> power(T a, u32 b) {
    T ans = 1;
    for (; b; b >>= 1) {
        if (b & 1) ans *= a;
        a *= a;
    }
    return ans;
}
template<class T> constexpr T TEN(u32 n) { return pow(static_cast<T>(10), n); }

// part of <bit> from C++20
#if __cplusplus <= 201703L
using bit_t = long long;
int countr_zero(bit_t n) { return n == 0 ? 64 : __builtin_ctzll(n); }
int countl_zero(bit_t n) { return n == 0 ? 64 : __builtin_clzll(n); }
int countr_one(bit_t n) { return n == std::numeric_limits<bit_t>::max() ? 64 : countr_zero(compl n); }
int countl_one(bit_t n) { return n == std::numeric_limits<bit_t>::max() ? 64 : countl_zero(compl n); }
constexpr bit_t mask(u32 n) { return (static_cast<bit_t>(1) << n) - 1; }
int popcount(bit_t n) { return __builtin_popcountll(n); }
bool is_pow2(bit_t n) { return (n & (n - 1)) == 0; }
int msb(bit_t n) { return 63 - countl_zero(n); }
bit_t bit_ceil(bit_t n) { return is_pow2(n) ? n : static_cast<bit_t>(1) << msb(n); }
bit_t bit_floor(bit_t n) { return is_pow2(n) ? n : static_cast<bit_t>(1) << (msb(n) + 1); }
#endif
// }}}

// chmax / chmin {{{
template<class T, class U = T> bool chmin(T& a, U&& b) { return b < a ? a = std::forward<U>(b), true : false; }
template<class T, class U = T> bool chmax(T& a, U&& b) { return a < b ? a = std::forward<U>(b), true : false; }
// }}}

// (rev)rep {{{
template<class T = size_t> struct rep {
    struct itr {
        T i, width;
        constexpr itr(const T i, const T width) noexcept: i(i), width(width) {}
        void operator++() noexcept { i += width; }
        constexpr T operator*() const noexcept { return i; }
        constexpr bool operator!=(const itr x) const noexcept { return i < x.i; }
    };
    const itr f, l;
    constexpr rep(const T f, const T l = std::numeric_limits<T>::max(), const T width = 1) noexcept
        : f(std::min(f, l), width), l(l, width) {}
    constexpr auto begin() const noexcept { return f; }
    constexpr auto end() const noexcept { return l; }
    constexpr auto collect_vec() noexcept {
        std::vector<T> ret{};
        for (auto&& i: *this) ret.emplace_back(i);
        return ret;
    }
};

template<class T = size_t> struct revrep {
    struct itr {
        T i;
        constexpr itr(const T i) noexcept: i(i) {}
        void operator++() noexcept { --i; }
        constexpr T operator*() const noexcept { return i; }
        constexpr bool operator!=(const itr x) const noexcept { return i != x.i; }
    };
    const itr f, l;
    constexpr revrep(const T f, const T l) noexcept: f(l - 1), l(std::min(f, l) - 1) {}
    constexpr auto begin() const noexcept { return f; }
    constexpr auto end() const noexcept { return l; }
};
// }}}

void main();

}  // namespace atr

// main() {{{
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout << std::fixed << std::setprecision(10);
    std::cerr << std::fixed << std::setprecision(10);
    atr::main();
}
// }}}

namespace atr {
#define LAMBDA(...) [&]([[maybe_unused]] const auto& $) { return __VA_ARGS__; }
#define LAMBDA2(...) [&](const auto& $1, const auto& $2) { return __VA_ARGS__; }

using namespace std;

// }}}

void main() {}

}  // namespace atr
atreeatree

入力の wrapper

右辺値を返す in()と左辺値参照を読むscan()
問題は operator>>(std::istream, T &t)(あってる?)がないT。というか実際 std::vector くらいしかない(modintもそうっぽいが ABC232-E で人々が事故してたことを鑑みるに別にこのままで良さそう。)ので scan_vecみたいなのでいい気がしてきた。読んだ時に関数を適用させるパターンも見つけた。
std::pair もそうだな。グラフも読めるとかなり嬉しさがある。(edge: std::vector<std::pair<usize, usize>>隣接グラフ / 隣接行列に変換するやつを書いた方が都合いいな、あとは 1-index を直す件)

template<typename T> inline T in() {
    T x;
    std::cin >> x;
    return x;
}

template<size_t I = 0, class Tp> Tp in(Tp tp) {
    if constexpr (I < std::tuple_size_v<Tp>) {
        std::tuple_element_t<I, Tp> ti;
        ti = in<decltype(ti)>();
        in<I + 1>(tp);
    } else {
        return tp;
    }
}

std::cinできる型とそれらからなる tuple-like な型と(以下再帰)は読める。

inline constexpr auto scan = [](auto&... args) { ((std::cin >> args), ...); };

template<class T> T in() {
    T x;
    scan(x);
    return x;
}

template<class T1, class T2, class... Args> std::tuple<T1, T2, Args...> in() {
    std::tuple<T1, T2, Args...> tp;
    std::apply(scan, tp);
    return tp;
}

とりあえずこれでいいかもしれない。だが改善点が二つ。

  • scan に tuple-like な型を渡せない。(彼らに operator>> を実装することで解決するが美しくない)tuple-like な型はそのままインスタンスになってほしい。
  • 1-indexed に対応できない。risujiroh さんは operator-- を適用できるようにしていた。せっかくstd::tupleなのでstd::applyすればよさそう。
    備忘録
  • make_vector の最後の引数に function object を渡せるようにすれば入力に使えそう。
atreeatree

argio みたいな出力

main の中でやるんじゃなくて別途 print, println みたいな関数を用意した方が都合よさそう。

要件

atreeatree

これもういいかな、正直そんな便利じゃない気がしてきた。作っても print(ln) だけ。

atreeatree
template<class, class = std::void_t<>> struct is_printable: std::false_type {};
template<class T> struct is_printable<T, std::void_t<decltype(std::cout << std::declval<T&>())>>: std::true_type {};

template<class, class = std::void_t<>> struct is_readable: std::false_type {};
template<class T> struct is_readable<T, std::void_t<decltype(std::cin >> std::declval<T>())>>: std::true_type {};

発明した(車輪の再開発)

atreeatree

みたま神さまの協力のおかげで欲しいものを作れた。

// Copyright (c) 2021 Mitama Lab | This code is based on code released under the MIT license; https://opensource.org/licenses/MIT
#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

template<class T, class = void> struct has_type: std::false_type {};
template<class T> struct has_type<T, std::void_t<typename std::decay_t<T>::type>>: std::true_type {};
template<class T, class = void> struct has_value: std::false_type {};
template<class T> struct has_value<T, std::void_t<decltype(std::decay_t<T>::value)>>: std::true_type {};
namespace detail {
template<class, class, class = void> struct is_tuple_like_detail: std::false_type {};
template<class T, std::size_t... I>
struct is_tuple_like_detail<T, std::index_sequence<I...>, std::void_t<decltype(std::get<I>(std::declval<T>()))...>>
    : std::conjunction<has_type<std::tuple_element<I, T>>...> {};
template<class Tp> struct is_tuple_like_impl: is_tuple_like_detail<Tp, std::make_index_sequence<std::tuple_size_v<Tp>>> {};
}  // namespace detail
template<class T> struct is_tuple_like: std::conjunction<has_value<std::tuple_size<T>>, detail::is_tuple_like_impl<T>> {};

// Copyright (c) 2021 Mitama Lab | This code is based on code released under the ISC license; https://opensource.org/licenses/ISC
#include <iostream>
#include <tuple>

template<class Tup, std::size_t... Idx> inline bool tuple_scan(Tup&, std::index_sequence<Idx...>);
inline auto scan = [](auto&... args) -> bool {
    return (
        [&] {
            if constexpr (is_tuple_like<std::decay_t<decltype(args)>>::value) {
                return tuple_scan(args, std::make_index_sequence<std::tuple_size_v<std::decay_t<decltype(args)>>>{});
            } else {
                return (std::cin >> args).fail();
            }
        }(),
        ...);
};

template<class Tup, std::size_t... Idx> inline bool tuple_scan(Tup& tup, std::index_sequence<Idx...>) {
    return (scan(std::get<Idx>(tup)) and ...);
}

template<class T> inline auto in() {
    T x;
    scan(x);
    return x;
}

template<class T, class Proj> inline auto in(Proj&& f) {
    auto x = in<T>();
    f(x);
    return x;
}

template<class... Args> std::enable_if_t<sizeof...(Args) >= 2, std::tuple<Args...>> inline in() {
    std::tuple<Args...> tp;
    std::apply(scan, tp);
    return tp;
}

template<class... Args, class Proj> std::enable_if_t<sizeof...(Args) >= 2, std::tuple<Args...>> inline in(Proj&& f) {
    auto tp = in<Args...>();
    std::apply([f](auto&&... a) { (f(a), ...); }, tp);
    return tp;
}

template<class... Args> inline auto offset1() {
    return in<Args...>([](auto& x) { --x; });
}

#include <boost/type_index.hpp>

int main() {
    const auto a = in<int>();
    const auto [b, c] = in<int, int>();
    const auto d = in<int>([](auto& x) { --x; });
    const auto [e, f] = in<int, int>([](auto& x) { --x; });
    const auto g = in<int>([](auto& x) { --x; });
    const auto [h, i] = in<int, std::pair<std::pair<int, int>, int>>();
    std::cout << boost::typeindex::type_id_with_cvr<decltype(a)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(b)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(c)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(d)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(e)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(f)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(g)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(h)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id_with_cvr<decltype(i)>().pretty_name() << std::endl;
}
atreeatree

これでどうだ!という感じ。いま知ったんだけど関数の返り値って decltype(auto) にすれば変えられるらしいので in を軽くリファクタしておくか。

constexpr char sep = ' ';
constexpr char eoln = '\n';

template<class T> auto print(T&&) -> void;

template<class Tp, std::size_t... Idx> inline auto tuple_print(const Tp& tp, std::index_sequence<Idx...>) -> void {
    (
        [&] {
            print(std::get<Idx>(tp));
            if constexpr (Idx + 1 != std::tuple_size_v<Tp>) print(sep);
        }(),
        ...);
}

template<class T> auto print(T&& x) -> void {
    if constexpr (is_printable<T>::value) {
        std::cout << x;
    } else if constexpr (is_tuple_like<T>::value) {
        tuple_print(x, std::make_index_sequence<std::tuple_size_v<T>>());
    } else if constexpr (is_iteratable<T>::value) {
        for (auto it = std::begin(x); it != std::end(x); ++it) {
            print(*it);
            if (next(it) != std::end(x)) print(sep);
        }
    } else {
        static_assert(true, "print: got an unexpented type.");
    }
}

template<class T, class U, class... Args> auto print(T&& t, U&& u, Args&&... args) {
    print(t);
    print(sep);
    print(u, args...);
}

template<class... Args> auto println(Args&&... args) {
    print(std::forward<Args>(args)...);
    print(eoln);
}
atreeatree

だん

// Copyright (c) 2021 Mitama Lab | This code is based on code released under the MIT license; https://opensource.org/licenses/MIT
#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

template<class T, class = void> struct has_type: std::false_type {};
template<class T> struct has_type<T, std::void_t<typename std::decay_t<T>::type>>: std::true_type {};
template<class T, class = void> struct has_value: std::false_type {};
template<class T> struct has_value<T, std::void_t<decltype(std::decay_t<T>::value)>>: std::true_type {};
namespace detail {
template<class, class, class = void> struct is_tuple_like_detail: std::false_type {};
template<class T, std::size_t... I>
struct is_tuple_like_detail<T, std::index_sequence<I...>, std::void_t<decltype(std::get<I>(std::declval<T>()))...>>
    : std::conjunction<has_type<std::tuple_element<I, T>>...> {};
template<class Tp> struct is_tuple_like_impl: is_tuple_like_detail<Tp, std::make_index_sequence<std::tuple_size_v<Tp>>> {};
}  // namespace detail
template<class T> struct is_tuple_like: std::conjunction<has_value<std::tuple_size<T>>, detail::is_tuple_like_impl<T>> {};

// Copyright (c) 2021 Mitama Lab | This code is based on code released under the ISC license; https://opensource.org/licenses/ISC
#include <iostream>
#include <tuple>

template<class Tup, std::size_t... Idx> inline bool tuple_scan(Tup&, std::index_sequence<Idx...>);
inline auto scan = [](auto&... args) -> bool {
    return (
        [&] {
            if constexpr (is_tuple_like<std::decay_t<decltype(args)>>::value) {
                return tuple_scan(args, std::make_index_sequence<std::tuple_size_v<std::decay_t<decltype(args)>>>{});
            } else {
                return (std::cin >> args).fail();
            }
        }(),
        ...);
};

template<class Tup, std::size_t... Idx> inline bool tuple_scan(Tup& tup, std::index_sequence<Idx...>) {
    return (scan(std::get<Idx>(tup)) and ...);
}

template<class T, class... Args> decltype(auto) inline in() {
    if constexpr (sizeof...(Args) == 0) {
        T x;
        scan(x);
        return x;
    } else {
        std::tuple<T, Args...> tp;
        scan(tp);
        return tp;
    }
}

template<class T, class... Args, class Proj> inline decltype(auto) in(Proj&& f) {
    if constexpr (sizeof...(Args) == 0) {
        auto x = in<T>();
        f(x);
        return x;
    } else {
        auto tp = in<T, Args...>();
        std::apply([f](auto&&... a) { (f(a), ...); }, tp);
        return tp;
    }
}

template<class... Args> inline auto offset1() {
    return in<Args...>([](auto& x) { --x; });
}
atreeatree
template<typename T, size_t N> auto Vec_impl(std::vector<size_t>& sizes, T& x) {
    if constexpr (N == 1) {
        if constexpr (std::is_invocable_v<T>) {
            std::vector<std::invoke_result_t<T>> ret(sizes[0]);
            for (auto it = std::begin(ret); it != std::end(ret); ++it) *it = x();
            return ret;
        } else {
            return std::vector(sizes[0], x);
        }
    } else {
        size_t size = sizes[N - 1];
        sizes.pop_back();
        return std::vector(size, Vec_impl<T, N - 1>(sizes, x));
    }
}

template<typename T, size_t N> auto Vec(const size_t (&sizes)[N], T&& x = T()) {
    std::vector<size_t> s(N);
    for (size_t i = 0; i < N; ++i) s[i] = sizes[N - i - 1];
    return Vec_impl<T, N>(s, x);
}

できたが、ちょっと難儀な点があって、二次元以上で使うと想定した挙動にならない。
たとえば、

1 2 3
4 5 6

auto v = Vec({2, 3}, in<int>);

などとしても

v == {{1, 2, 3}, {1, 2, 3}}

になる。それはそうだし直すのも大変そうなので [[deprecated]] しておくというのは嘘で、C++ はユーザー定義 waring を提供していないっぽい。

このスクラップは2021/12/22にクローズされました