Fundamental Types And Compound Types#
Integer#
Char#
char
is not guaranteed to be signed or unsigned.- If you hope to use exact signed one, you need to explicitly use
signed char
.
Signed Integers Overflow#
- It’s UB for signed integers to overflow.
- you can use
-ftrapv
option to trap for signed overflow in addition.
int a = std::numeric_limits<int>::max();int b = 1;int result = a + b;std::println("Result of a + b: {}", result);
Unsigned Integer is Always >= 0#
- There are many hidden bugs related to this, e.g.
std::string::npos
. std::in_range(x)
can be used to check whether a value is representable by integer typeT
.
Integer Promote#
- All arithmetic operations will promote integers that are smaller than
int
toint
first.
uint32_t a32 = 0xffffffff, b32 = 0x00000001, c32 = 0xffffffff;uint8_t a8 = 0xff, b8 = 0x01, c8 = 0xff;std::println("sizeof(int) : {}", sizeof(int));std::println("uint32_t : {}", a32 + b32 > c32 ? "unexpected!" : "ok");std::println("uint8_t : {}", a8 + b8 > c8 ? "unexpected!" : "ok");
The Size of Integers#
-
What’s the size of integers? (int/long/pointer)
- ILP32(4/4/4): Widely used in 32-bit system
- LLP64(4/4/8): Widely used in Windows
- LP64(4/8/8): Widely used in Linux and MacOS
-
get special values for an arithmetic type, you may use
<limits>
std::numeric_limits::max()
get the maximum finite number. (Be careful when using it with floating points)
Bit Manipulation#
<bit>
: for unsigned integers.- Use
std::endian
to check the endianness of platform. - Use
std::byteswap
to swap an integer byte by byte.
Literal Prefix and Suffix#
10
– decimal0x10
– hexadecimal (get 16)010
– octal (get 8)0b0011'0100
– binary (get 2)1
–int
;1l, 1L
–long
;1ll, 1LL
-long long
1u, 1ull, 1llu
-unsigned
- Separator is supported, i.e.
0x11FF’3344
;0b0011’0100
Bool#
- Special integer, only true (non-zero, not definitely 1) or false.
- Convert it to other integer types will get 1/0.
sizeof(bool)
is not necessarily 1bool
is not allowed to++
/--
since C++17.
Floating points#
What is it?#
-
sign bit + exponent field + fraction field
float
: 23 fraction + 8 exponentdouble
: 52 fraction + 11 exponent
-
0
-0
inf
-inf
NaN
-
&&/|| NaN
istrue
-
inf+(-inf)= NaN
-
<stdfloat>
: They are all different types rather than alias!std::float16_t
: 10+5std::float32_t
: 23+8std::float64_t
: 52+11std::float128_t
: 112+15std::bfloat16_t
: 7+8
Accuracy#
- For normalized numbers of float, plus and minus will have effects iff. their magnitude is in
float a = 0.1f; // -> 0.100000001490116119384765625double z = tan(pi/2.0); // -> 16331239353195370.0, not inf
Bit Manipulation#
std::bit_cast<ToType>(fromVal)
to unsigned integer, then use<bit>
Literal Prefix and Suffix#
- For floating point,
1e-5
means . 1.0
–double
;1.0f
–float
;1.0L
–long double
.1
means0.1
(zero before fraction can be omitted).
Compound Types#
cv-qualifier#
const
andvolatile
.volatile
is used to force the compiler to always get the content from memory.
Array-to-pointer Conversion (decay)#
- The following three function declaration forms are completely equivalent, all decay to pointer
void arraytest(int *a);void arraytest(int a[]);void arraytest(int a[3]);
- The following retain the dimension information of the array (using references)
void arraytest(int (&a)[3]);
VLA is Not Allowed#
- VLA (i.e.
int arr[n]
) is not allowed in C++
Enumeration#
-
You need to ensure not to exceed the limit of enumeration value in the meaning of bits (i.e.
(1 << std::bitwidth(MaxEnum)) – 1
), otherwise UB. -
For scoped enumeration, only comparisons are permitted; arithmetic operations will trigger compile error.
-
If you really want to do integer operations, you need to convert it explicitly.
-
use
std::underlying_type::type
orstd::underlying_type_t
to get the integer type. -
use
std::to_underlying(day)
to get the underlying integer directly;
Expression#
- Parameters in function are evaluated indeterminately, i.e. every sub-tree represented by the parameter is fully evaluated in a non-overlap way!
- Every overloaded operator has the same evaluation rule as the built-in operators, rather than only be treated as a function call.
- For
E1[E2]
,E1.E2
,E1 << E2
,E1 >> E2
,E1
is always fully evaluated beforeE2
. - For
E1 = E2
,E1 @= E2
,E2
is always fully evaluated beforeE1
.
Class#
Ctor and Dtor#
Initialization#
-
Reasons for using Uniform Initialization
{}
:- (Almost) all initialization can be done by curly bracket
{}
- Compared to
()
, it will strictly check Narrowing Conversion - It can also prevent most vexing parse
- (Almost) all initialization can be done by curly bracket
-
int a;
vsint a {};
: Compared to default initialization, value initialization will zero-initialize those do not have a default ctor. -
Notice that
auto
will behave in a weird way.- e.g.
auto a = {1}
, auto is initializer list!
- e.g.
-
Personally, I recommend not mixing up
auto
and{}
;
Ctor#
-
Memder has been default-initialized before ctor enters the function body.
-
Use member initializer list or In-Class Member Initializer.
-
It’s dangerous if you use members behind to initialize previous members in ctor! ->
-Wall
-
explicit
is used to prevent the compiler from using the constructor for implicit conversions.
Copy Ctor#
// Copy CtorClass(const Class& another) {}// operator=Class& operator=(const Class& another) { /*do something*/ return *this;}
- Pay attention to self-assignment, especially when you use pointers.
Member Functions#
- All non-static member functions in a class implicitly have a this pointer (with the type
Class*
) as parameter. - Sometimes you may hope methods unable to modify data members, then you need a const.
- This is achieved by make
this
to beconst Class*
.
- This is achieved by make
- But, what if what we change is just status that user cannot know?
- You may modify
mutable
variable inconst
method. - e.g.
mutable Mutex myLock;
- Use
mutable
carefully in restricted cases.
- You may modify
Inheritance#
Polymorphism#
- In C++, polymorphism is valid only in pointer and reference.
Virtual Methods#
- you should usually make dtor of base class
virtual
. - You can change the access control specifier of virtual methods, but it’ll collapse the access barrier, so it should be avoided to use.
override
and final
#
-
It’s recommended to use
override
. -
The meaning of “override” is not specialized for virtual methods like the keyword
override
.- If you name a non-virtual function with the same name as parent’s, you’ll also override it.
- You need to use
Parent::Func()
to call the parent version.
-
There is another similar specifier
final
.- It means override, and the derived class cannot override again.
- It may do optimization in virtual table. (devirtualization)
Virtual Methods With Default Params#
- When virtual methods have default params, calling methods of
Derived*/&
byBase*/&
will fill in the default param ofBase
methods!
void Parent::go(int i = 2) { std::println("Base's go with i = {}.", i);}void Child::go(int i = 4) { std::println("Derived's go with i = {}.", i);}
int main() { Child child; child.go(); Parent& childRef = child; childRef.go();
return 0;}
Template Method Pattern and CRTP#
- There is a pattern called template method pattern that utilize private virtual method. What derived classes need to do is just overriding those virtual methods.
class Student {public: float getGpa() const { return getGpaCoeff() * 4.0f; }private: virtual float getGpaCoeff() const { return 1.0f; }};
class Tom : public Student { float getGpaCoeff() const override { return 0.8f; }};
- CRTP (curiously recurring template pattern)
template <typename Derived>class Student {public: float getGpa() { return static_cast<Derived*>(this)->getGpaCoeff() * 4.0f; }};
class Tom : public Student<Tom> {public: float getGpaCoeff() const { return 0.8f; }};
Pure Virtual Function#
- It’s UB to call pure virtual function by vptr.
- Don’t call any virtual function and any function that calls virtual function in ctor & dtor!
class Base {public: Base() { reallyDoIt(); // calls virtual function! } void reallyDoIt() { doIt(); } virtual void doIt() const = 0;};
class Derived : public Base { void doIt() const override {}};
int main() { Derived d; // error!
return 0;}
- Pure virtual functions can have definition, but it should be split from the prototype in the class.
- if you define a pure virtual dtor, it must have a definition (though likely do nothing).
- if you hope to provide a default behavior, but don’t want the derived class to directly accept it.
Struct#
struct
is almost same as class in C++, except thatstruct
usepublic
as the default access control.- Aggregate struct can use designated initialization.
- Only data members exist and are all public.
- They shouldn’t have member functions. At most it has ctor & dtor & some operators.
- Array cannot use designated initialization though it’s an aggregate.
- There exists a special kind of “
struct
” called bit field- If bit amount exceeds the type(e.g. use 9 bits for
unsigned char
), it will be truncate to the max bits. - Much syntax in normal
struct
in C++ is invalid in bit field, and you may treat it as C-likestruct
. e.g. reference.
- If bit amount exceeds the type(e.g. use 9 bits for
Function Overloading#
- This is done by compilers using a technique called name mangling.
- Return type does NOT participate in name mangling. (Except that it’s a template parameter, since all template parameters are part of mangled name.)
Operator Overloading#
Ambiguity#
- Conversion operator may cause ambiguity so that the compiler reports error. ->
explicit
class Real {public: Real(float f) : // use explicit to avoid ambiguity val { f } { } Real operator+(const Real& a) const { return Real{val + a.val}; } operator float() { return val; }private: float val;};
int main() { Real a { 1.0f }; Real b = a + Real{0.1f}; // ok // Real b = a + 0.1f; // error, ambiguous
return 0;}
Three-way Comparison <=>
#
struct Data { int id; float val; auto operator<=>(const Data& another) const { return val <=> another.val; } bool operator==(const Data& another) const { return val == another.val; }};
int main() { Data a { 0, 0.1f }; Data b { 1, 0.2f }; a < b; a <= b; a > b; a >= b; // boost by <=> a == b; a != b; // boost by ==
return 0;}
-
<=>
actually returnsstd::strong_ordering
,std::weak_ordering
orstd::partial_ordering
- The ordering can be compared with 0.
- You can also use bool
std::is_lt/eq/gt/lteq/gteq/neq(ordering)
to judge what it is.
-
You may notice that
<=>
is enough to know==
, so why do we need to overload it manually?==
may be cheaper.
Lambda Expression#
- Since C++11, lambda expression is added as “temporary functor” or closure.
- It is just an anonymous
struct
generated by the compiler, with anoperator() const
.
- It is just an anonymous
int main() { int copy { 0 }; int ref { 0 }; auto f { [c = copy, &ref]() { return c + ref; } }; f(); return 0;}// same asint main() { int copy { 0 }; int ref { 0 };
class __lambda_5_9 { public: inline /*constexpr */ int operator()() const { return c + ref; }
private: int c; int& ref;
public: __lambda_5_9(int& _c, int& _ref): c { _c }, ref { _ref } {} };
__lambda_5_9 f = { __lambda_5_9 { copy, ref } }; f.operator()(); return 0;}
-
If you use lambda expression in a non-static class method and you want to access all of its data members, you may need to explicitly capture
this
(by reference, since only copy pointer) or*this
(really copy all members).- It’s recommended to capture data members directly.
-
You may add specifiers after ().
- mutable: since C++17, remove
const
inoperator()
, i.e. for capture by value, you can modify them (but don’t really affect captured variable). - static: since C++23, same as
static operator()
(it’s always better to use it if you have no capture!). constexpr/consteval/noexcept
: we’ll introduce them in the future.
- mutable: since C++17, remove
-
It’s also legal to add attributes between
[]
and()
.
Improvement in Execution Flow#
if(using T = xx; …);
for(auto vec = getVec(); auto& m : vec);
using enum VeryLongName;
[[fallthrough]];
enum class VeryLongName { LONG, LONG_LONG, LONG_LONG_LONG,};auto val = VeryLongName::LONG;switch (val) { using enum VeryLongName; case LONG: foo(); [[fallthrough]]; case LONG_LONG: foo(); break; case LONG_LONG_LONG: foo(); break; default: break;}
Template#
- Since C++20, abbreviated function template is introduced.
void foo(const auto& a, const auto& b) {}// same astemplate <typename T1, typename T2>void foo(const T1& a, const T2& b) {}
- Lambda expression can also use template.
auto less = [](const auto& a, const auto& b) static { return a < b; };auto less = []<typename T>(const T& a, const T& b) static { return a < b; };
- It’s strongly recommended not mixing abbreviated template with template, which will cause many subtle problems.
Homework#
上半部分#
1#
使用gcc(善用compiler explorer)编译并运行下面的程序:
#include <limits>int main(){ int a = std::numeric_limits<int>::max(); int b = a + 1; return 0;}
随后尝试加上编译选项-ftrapv
,看看运行结果有没有变化。
Answer#
见笔记.
2#
Answer#
打印一个std::uint8_t
的变量的地址;如果你想用print
,可以使用std::println("{}", 你的地址)
。
uint8_t a = 0;std::println("{}", static_cast<void *>(&a));
3#
在2001年的游戏《雷神之锤3》(Quake III Arena)中,使用了一种快速倒数平方根算法,用于对于32位浮点数x
计算的近似值。它的算法如下:
/* 设操作BitEquiv表示两个数在二进制表示上相同, i32表示32位整数,f32表示32位浮点数。*/f32 GetInvSqrt(f32 num){ i32 a <- BitEquiv(num); i32 b <- 0x5f3759df - (a >> 1); f32 y <- BitEquiv(b); return y * (1.5f - (num * 0.5f * y * y));}
试把上述伪代码转为C++代码。可以尝试几个数,看看和<cmath>
中的std::sqrtf
相比的误差;如果你感兴趣,也可以在quick-bench上比一比性能。
当然,这个算法在目前是比CPU硬件指令更慢的,只是在2000年代更好。它本质上使用了牛顿迭代法,
return
的步骤进行了一次迭代,如果想要更高的精度可以把它赋给y
,继续用该式迭代。
Answer#
float getInvSqrt(float num) { uint32_t a = std::bit_cast<uint32_t>(num); uint32_t b = 0x5f3759df - (a >> 1); float y = std::bit_cast<float>(b);
return y * (1.5f - (num * 0.5f * y * y));}
int main() { std::println("getInvSqrt : {}", getInvSqrt(0.1f)); std::println("std::sqrtf : {}", 1.0f / std::sqrtf(0.1f));
return 0;}
4#
写一个将二进制数据转为HTTP要求的字节序的函数。注意用std::endian
区分当前机器大小端的情况;如果两个都不是,打印一个警告。
Answer#
template <std::integral T>T toHttpData(T input) { if constexpr (std::endian::native == std::endian::big) { return input; } else if constexpr (std::endian::native == std::endian::little) { return std::byteswap(input); } else { std::println("warning!"); }}
int main() { std::println("{:#x}", toHttpData(0x112233)); return 0;}
5#
判断以下程序的合法性:
int a[][3] = { {1, 2, 3}, {4,5,6} };int (*b)[3] = a; // 合法吗?int **c = a; // 合法吗?int **d = b; // 合法吗?
int a[]{1,2,3}, b[]{4,5,6,7};void Func(int (&a)[3]) { /* ... */}void Func2(int a[3]) { /* ... */}
Func(a); Func2(a);Func(b); Func2(b); // 这四个里面哪个合法?
希望你可以通过这个例子区分指针和数组,加深对数组decay的理解;指针所指向的目标不会继续decay,引用所引用的目标也会暂时保持原类型(不过仍然允许后续的decay,比如上面的Func
中int* p = a;
是合法的)。
Answer#
int a[][3] = { {1, 2, 3}, {4,5,6} };int (*b)[3] = a; // 合法int **c = a; // 不合法, a -> int[2][3]int **d = b; // 不合法, b -> int (*)[3]
int a[]{1,2,3}, b[]{4,5,6,7};void Func(int (&a)[3]) { /* ... */}void Func2(int a[3]) { /* ... */}
Func(a); // 合法Func(b); // 不合法Func2(a); // 合法Func2(b); // 合法
6#
写一个函数,它接受一个返回值为int
、参数为float
和double
的函数指针为参数,返回void
。
Answer#
using Func = int(*)(float, double);
void foo([[maybe_unused]] Func func) { std::println("do something");}
7#
写一个scoped enumeration,它包含read, write, exec
三个选项;同时为了使它们可以按位组合,编写一个配套的重载的按位与和按位或的运算符。为了一般性,可以使用auto b = std::to_underlying()
;如果你的编译器不支持,可以使用using T = std::underlying_type<Enum>
,再手动转为T
。
Answer#
enum class access_t : std::uint8_t { read = 1, write = 2, exec = 4,};
access_t operator&(access_t a, access_t b) { return static_cast<access_t>(std::to_underlying(a) & std::to_underlying(b));}
access_t operator|(access_t a, access_t b) { return static_cast<access_t>(std::to_underlying(a) | std::to_underlying(b));}
8#
对于下面的程序:
#include <map>int main(){ std::map<int, int> m; m[0] = m.size();}
按照C++17标准规定,m[0]
是什么?
Answer#
#include <map>int main() { std::map<int, int> m; m[0] = m.size(); // m[0] -> 0,}
- 先计算右侧
m.size()
, 此时m
为空, 返回0 - 再求左侧
m[0]
, 插入键 0 并值初始化为 0, 此时m.size()
变为 1 - 最后将右侧结果 0 赋值给
m[0]
, 故m[0]
最终值为 0
下半部分#
1#
写一个Vector3
类,它还有三个float分量,可以用不超过3个float进行初始化,例如:
Vector3 vec; // (0, 0, 0)Vector3 vec2{1}; // (1, 0, 0)Vector3 vec3{1, 2}; // (1, 2, 0)
同时,定义operator+
和operator+=
。试一试,1+a
在operator+
写成成员函数时是否可用?写成全局函数时呢?给构造函数加上explicit
时呢?
这是因为
1
作为函数的参数,进行的是copy initialization,这正是explicit所禁止的;必须写为Vector3{1}
才可以。
随后,我们增加比较运算符,比较两个向量的长度。用简短的方法使六个比较运算符都有效。
别忘了用属性警告用户某些运算符的返回值不应被抛弃。
Answer#
#include <print>#include <cmath>
class Vector3 {public: Vector3(float a = 0.0f, float b = 0.0f, float c = 0.0f) : val { a, b, c } { }
void display() const { std::println("val: {}, {}, {}; len: {}", val[0], val[1], val[2], length()); }
[[nodiscard]] Vector3 operator+(const Vector3& another) const { return { val[0] + another.val[0], val[1] + another.val[1], val[2] + another.val[2], }; } [[nodiscard]] Vector3 operator+(const float num) const { return { val[0] + num, val[1] + num, val[2] + num, }; } [[nodiscard]] friend Vector3 operator+(const float num, const Vector3& vec) { return vec + num; }
Vector3& operator+=(const Vector3& another) { val[0] += another.val[0]; val[1] += another.val[1]; val[2] += another.val[2]; return *this; } Vector3& operator+=(const float num) { val[0] += num; val[1] += num; val[2] += num; return *this; }
[[nodiscard]] bool operator==(const Vector3& another) const { return length() == another.length(); } [[nodiscard]] bool operator==(const float len) const { return length() == len; } [[nodiscard]] auto operator<=>(const Vector3& another) const { return length() <=> another.length(); } [[nodiscard]] auto operator<=>(const float len) const { return length() <=> len; }
private: float length() const { return std::hypot<float>(val[0], val[1], val[2]); }
private: float val[3];
};
int main() { Vector3 vec1; Vector3 vec2 { 1 }; Vector3 vec3 { 1, 2 };
auto vec = vec1 + vec3; vec.display(); vec = 1 + vec2; vec.display(); vec += 1; vec.display();
std::println( "{}, {}, {}, {}, {}, {}", vec < vec1, 1.0f <= vec1, vec > 1.0f, vec >= vec1, vec == vec1, 1.0f != vec1 );
return 0;}
2#
除了vector
,其他的容器可以使用Uniform initialization吗?特别地,map
和unordered_map
每个元素是一个pair。
Answer#
可以
3#
有虚函数的类和普通的类相比,有大小上的差别吗?
Answer#
有, 可能可以优化到没有
4#
下面的两个函数构成重载吗?
void Test(int);int Test(int);
下面两个呢?T = void
会有干扰吗?
void Test(int);template<typename T> T Test(int);
想一想name mangling的规则,得出你的结论,在编译期上进行测试。
Answer#
template<typename T = void>T Test(const int a) { if constexpr (std::is_same_v<T, float>) { std::println("float {}", a); return 0.0f; } else if constexpr (std::is_same_v<T, void>) { std::println("void {}", a); return; } else { static_assert(false); }}void Test(const int a) { std::println("int {}", a);}
int main() { Test(1); Test<>(2); // only for "T = void" Test<void>(3); Test<float>(4); // Test<int>(5); // error return 0;}
5#
下面的程序中,bar
返回什么是确定的吗?分别在gcc无优化选项和-O2
中试试。
struct Foo{ int foo() { return this ? 42 : 0; }};
int bar(){ Foo* fptr = nullptr; return fptr->foo();}
这道题的目的是告诉你不要写UB,空指针不能解引用。
Answer#
是 UB (恼
6#
写一个三维数组,以存储的类型为模板参数,内部使用摊平的一维连续数组存储,允许使用多维下标运算符进行访问。定义它的拷贝构造函数,想想是否要考虑自赋值问题。
Answer#
#include <print>#include <array>
template <typename T, std::size_t Dx, std::size_t Dy, std::size_t Dz>class Array3 {private: constexpr static std::size_t D = Dx * Dy * Dz;public: Array3() = default; Array3(const std::array<T, D>& a) : arr { a } { } Array3(const std::initializer_list<T>& l) { if (l.size() != D) { throw std::invalid_argument("Initializer list size mismatch"); } else { std::copy(l.begin(), l.end(), arr.data()); } } Array3(const Array3<T, Dx, Dy, Dz>& another) : arr { another.arr } { } Array3& operator=(const Array3<T, Dx, Dy, Dz>& another) { arr = another.arr; return *this; } [[nodiscard]] T& operator[](const std::size_t dx, const std::size_t dy, const std::size_t dz) { if (not (dx < Dx and dy < Dy and dz < Dz)) { throw std::out_of_range("Index out of range"); } return arr[dx * Dy * Dz + dy * Dz + dz]; } void display() { for (auto& m : arr) { std::print("{} ", m); } std::println(""); }private: std::array<T, D> arr;};
template <std::size_t Dx, std::size_t Dy, std::size_t Dz>using Array3f = Array3<float, Dx, Dy, Dz>;
int main() { Array3f<1, 3, 2> a { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f }; auto b = a; a.display(); b.display();
b[0, 1, 0] = 3.0f; a = b; a.display(); b.display();
return 0;}
不涉及指针, 没有自赋值问题.
7#
写一个函数,它接受std::vector<int>&
,使用<algorithm>
中的std::ranges::generate
在其中填充斐波那契数列。你只需要填写第二个参数,即一个lambda表达式:
void FillFibonacci(std::vector<int>& v){ std::ranges::generate(v, /* 你的回答 */);}
可以认为上面的式子相当于:
for(auto& elem : v) elem = YourLambda();
提示:你可以在capture中创建新的变量,例如
[a = 0, b = 1]
。
Answer#
void fillFibonacci(std::vector<unsigned int>& v) { std::ranges::generate(v, []() { static unsigned int a = 0; static unsigned int b = 1; unsigned int ret = a; a = b; b = ret + b; return ret; });}
8#
定义一个scoped enumeration Color
,有red, blue, green三个枚举量。编写一个switch
,使得red
和green
都打印Hello
,对blue
打印World
。使用using enum
来避免每个枚举都写Color::
,并使用属性防止编译器对fallthrough给出警告。
Answer#
enum class Color { RED, BLUE, GREEN,};
void say(const Color& c) { switch (c) { using enum Color; case RED : [[fallthrough]]; case BLUE : std::println("Hello"); break; case GREEN : std::println("World"); break; default : throw std::logic_error(""); break; }}
9#
对下面的结构体的数组调用sort,使用lambda表达式进行比较。
struct DijkstraInfo{ int vertexID; int distance; // 作为比较的标准};
std::ranges::sort(vec, /* 你的lambda表达式 */ );
想一想,是否可以把两个参数类型都写成const auto&
?
如果你写成一个函数:
bool Func(const auto& a, const auto& b);
它能否像lambda表达式一样传入sort
?还是需要实例化?为什么?
如果你写成一个下面的结构体:
template<typename T>struct Functor{ // 如果你的编译器暂时不支持static operator(),把static去掉. static bool operator()(const T& a, const T& b){ /* ... */ }};
它能否像lambda表达式一样传入sort
?还是需要实例化?为什么?
希望你通过这个例子理解模板实例化的要求, 可以通过包装在无需实例化的实体中绕过限制, 推迟到调用的时候再实例化, 此时就不需要程序员手动实例化.
Answer#
#include <print>#include <vector>#include <algorithm>
struct DijkstraInfo { int vertexID; int distance;};
struct FunctorTin { template<typename T> static bool operator()(const T& a, const T& b) { return a.distance < b.distance; }};template<typename T>struct FunctorTout { static bool operator()(const T& a, const T& b) { return a.distance < b.distance; }};
template <typename T>bool Func(const T& a, const T& b) { return a.distance < b.distance;}bool FuncAuto(const auto& a, const auto& b) { return a.distance < b.distance;}
int main() { std::vector<DijkstraInfo> vec; vec.resize(10);
vec[0].distance = 3; vec[2].distance = 2; vec[3].distance = 8; vec[5].distance = 2; vec[7].distance = 6; vec[9].distance = 5; for (std::size_t i = 0; i < 10; i++) { vec[i].vertexID = static_cast<int>(i); std::println("id: {}, d: {}", vec[i].vertexID, vec[i].distance); } std::println("");
auto LambdaFunc = [](const auto& a, const auto& b) { return a.distance < b.distance; };
std::ranges::sort(vec, LambdaFunc); std::ranges::sort(vec, FunctorTin()); std::ranges::sort(vec, FunctorTout<DijkstraInfo>()); std::ranges::sort(vec, Func<DijkstraInfo>); std::ranges::sort(vec, FuncAuto<DijkstraInfo, DijkstraInfo>);
for (auto& m: vec) { std::println("id: {}, d: {}", m.vertexID, m.distance); }
return 0;}
通过结构体包装, 延迟实例化. 如果我们将模板函数封装在一个结构体中, 编译器可以在调用时根据需要进行模板实例化.
10#
解答Lee的问题; 你应该如果解决?
Answer#
因为传了引用.