C++ 元编程技术笔记

Caution

这里纯粹是曾经的快速笔记,不保证质量,罗列于此仅供触类旁通。

使用模板类作为模板参数

using template class as template parameters

C++ template template parameter - Stack Overflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename X>
struct GenericThing
{
    X data;
};

template<template<typename> class T, typename E>
struct Bar
{
    T<E> sub; // instantiate T with E
    Bar() : sub() { cout << "Bar" << endl; }
};

int main()
{
    Bar<GenericThing, int> intbar;
    Bar<GenericThing, float> floatbar;
    return 0;
}

定义别名时注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T, typename = void>
struct gg {
};

template<template<typename, typename...> class Source, typename T, typename... TArgs>
struct wrapper {
    using Source<T, TArgs...>::Source;

    typedef T value_type;
    template<typename T_, typename... Args_>
    using base_type_t = Source<T_, Args_...>;
    using self_type_t = wrapper;
    using base_type = Source<T, TArgs...>;
    using self_type = wrapper<Source, T, TArgs...>;
};

template<typename T>
using ggw = wrapper<gg, T>;
// 此时注意不要使用 wrapper<gg<T>, T> 这样的写法

继承基类的构造函数

using 声明 - cppreference.com

1
2
using typename(可选) 嵌套名说明符 无限定标识 ;		(C++17 )
using 声明符列表 ;		(C++17 )

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
 
// 基类 B
struct B
{
    virtual void f(int) { std::cout << "B::f\n"; }
    void g(char)        { std::cout << "B::g(char)\n"; }
    void h(int)         { std::cout << "B::h\n"; }
protected:
    int m; // B::m 是受保护的
    typedef int value_type;
};
 
// 派生类 D
struct D : B
{
    using B::m; // D::m 是公开的
    using B::value_type; // D::value_type 是公开的
 
    // using 的非必要使用场合:
    // B::f 已经公开,它作为 D::f 公开可用,
    // 并且下面的 D::f 和上面的 B::f 有相同的签名。
    using B::f;
    void f(int) { std::cout << "D::f\n"; } // D::f(int) **覆盖** B::f(int)
 
    // using 的必要使用场合:
    // B::g(char) 被 D::g(int) 隐藏,
    // 除非它在这里通过 using B::g 显式暴露为 D::g(char)。 
    // 如果不用 using B::g,将 char 传递给 D::g(char) 实际上调用的是下面定义的 D::g(int),
    // 因为后者隐藏了 B::g(char),并且 char 形参会因此隐式转型到 int。
    using B::g; // 将 B::g(char) 作为 D::g(char) 暴露,
                // 后者与下面定义的 D::g(int) 完全是不同的函数。
    void g(int) { std::cout << "D::g(int)\n"; } // g(int) 与 g(char) 均作为 D 的成员可见
 
    // using 的非必要使用场合:
    // B::h 已经公开,它作为 D::h 公开可用,
    // 并且下面的 D::h 和上面的 B::h 有相同的签名。
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) **隐藏** B::h(int)
};
 
int main()
{
    D d;
    B& b = d;
 
//  b.m = 2;  // 错误:B::m 受保护
    d.m = 1;  // 受保护的 B::m 可以作为公开的 D::m 访问
 
    b.f(1);   // 调用派生类的 f()
    d.f(1);   // 调用派生类的 f()
    std::cout << "----------\n";
 
    d.g(1);   // 调用派生类的 g(int)
    d.g('a'); // 调用基类的 g(char),它**只是因为**
              // 派生类中用到了 using B::g; 才会暴露
    std::cout << "----------\n";
 
    b.h(1);   // 调用基类的 h()
    d.h(1);   // 调用派生类的 h()
}

Extract…

template parameter class

1.

You can use this:

1
2
3
4
5
6
7
8
9
10
11
template<typename T>
struct extract_value_type //lets call it extract_value_type
{
    typedef T value_type;
};

template<template<typename> class X, typename T>
struct extract_value_type<X<T>>   //specialization
{
    typedef T value_type;
};

It should work as long as the template argument to extract_value_type is of the form of either T or X<T>. It will not work for X<T,U>, however. But then it is easy to implement it in C++11 using variadic template.

Use it as:

1
2
3
4
5
template <typename T>
struct MyTemplate
{
    typedef typename extract_value_type<T>::value_type value_type;
};

Online demo : http://ideone.com/mbyvj


Now in C++11, you can use variadic template to make extract_value_type work with class templates which take more than one template arguments, such as std::vector, std::set, std::list etc.

1
2
3
4
5
template<template<typename, typename ...> class X, typename T, typename ...Args>
struct extract_value_type<X<T, Args...>>   //specialization
{
    typedef T value_type;
};

Demo : http://ideone.com/SDEgq

Constraint…

收集和研究各种约束手法

template type to copy-assignable

要求模板参数类必须为带有拷贝构造函数实现的。

From: https://stackoverflow.com/questions/63802972/c-how-to-constrain-template-type-to-copy-assignable-types

static_assert

You can also use static_assert for this, which lets you generate a nicer error message:

1
2
3
4
5
6
7
template<typename T>
class A
{
    static_assert (std::is_copy_assignable_v<T>, "T must be copy-assignable");
};


enable_if

You can use std::enable_if like this:

1
2
3
template<typename T, 
  typename = std::enable_if_t<std::is_copy_assignable_v<T>, void>> 
class A {};

Here’s a demo on Compiler Explorer using the following code:

1
2
3
4
5
6
7
8
9
10
11
#include<type_traits>
#include<ostream>

template<typename T, 
  typename = std::enable_if_t<std::is_copy_assignable_v<T>, void>> 
class A {};

int main() {
    A<int> a;
    // A<std::ostream> b; // error
}

In c++20, you could write:

1
2
3
template<typename T> 
  requires std::is_copy_assignable_v<T> 
class A {};

which is easier to read, and produces a better error message.

And here’s a demo of that:

1
2
3
4
5
6
7
8
9
10
11
#include<type_traits>
#include<ostream>

template<typename T> 
  requires std::is_copy_assignable_v<T> 
class A {};

int main() {
    A<int> a;
    // A<std::ostream> b; // error
}

good

注意,一个 class 中会有自动生成的复制构造函数,除非:

  • 被显式地 delete 了

    1
    2
    3
    4
    
    class A {
      public:
      A(A const&) = delete;
    };
    
  • 某个基类无法复制构造

所以可以采用辅助的 enable_if 模板参数来进行约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T,
typename = std::enable_if_t<std::is_copy_assignable_v<T>, void>>
  class A2 {};

inline A2<int> test_A2() {
  A2<int> a2;
  return a2;
}

class T1{};

inline test_T1(){
  A2<T1> a2;
  return a2;
}

iterator_traits

例子 1

1
2
3
4
5
6
7
8
9
10
11
// iterator_traits example
#include <iostream>     // std::cout
#include <iterator>     // std::iterator_traits
#include <typeinfo>     // typeid

int main() {
  typedef std::iterator_traits<int*> traits;
  if (typeid(traits::iterator_category)==typeid(std::random_access_iterator_tag))
    std::cout << "int* is a random-access iterator";
  return 0;
}

iterator_traits 往往被用在 iterator 实现类中,以便能取出一个代名词(通过类型别名),例如 pointer 能够更好地指代用户类型的指针形式,这比直接使用 T* 要更具备可读性以及可写性。

1
2
3
4
5
6
7
8
9
template <class T>
struct iterator_traits {
  typedef typename T::iterator_category iterator_category;
  typedef typename T::value_type value_type;
  typedef typename T::difference_type difference_type;
  typedef typename T::iterator_category iterator_category;
  typedef typename T::pointer pointer;
  typedef typename T::reference reference;
};

std::iterator<> 通过模板参数以及内部声明语句向其提供所需的依赖类型,所以一般来说这些类型(例如 T::value_type)无需你显式声明。

iterator_traits 一次性地提供一组类型别名,这些别名也可以被你的类实体所使用。

1
2
3
class TT{};

assert(std::is_same<std::iterator_traits<TT>::pointer, TT*>);

精简后的 iterator_traits 是与如下实现代码相等价(或者相近似)的:

1
2
3
4
5
6
7
8
9
template <typename T>
struct iterator_traits
{
    typedef std::random_access_iterator_tag iterator_category;
    typedef T                               value_type;
    typedef T*                              pointer;
    typedef T&                              reference;
    typedef std::ptrdiff_t                  difference_type;
};

Sanitize & Google Sanitizers

sanitize 是一种动态代码分析技术。

一般来说,动态代码分析技术手段包含这些工具和相关技术:

  • Google Sanitizers
  • Valgrind memcheck
  • Profiler - CMake Profiling
  • Code coverage

Google Sanitizers

Sanitizers are tools that perform checks during a program’s runtime and returns issues, and as such, along with unit testing, code coverage and static analysis, is another tool to add to the programmers toolbox. And of course, like the previous tools, are tragically simple to add into any project using CMake, allowing any project and developer to quickly and easily use.

A quick rundown of the tools available, and what they do:

  • LeakSanitizer detects memory leaks, or issues where memory is allocated and never deallocated, causing programs to slowly consume more and more memory, eventually leading to a crash.

  • AddressSanitizer

    is a fast memory error detector. It is useful for detecting most issues dealing with memory, such as:

    • Out of bounds accesses to heap, stack, global
    • Use after free
    • Use after return
    • Use after scope
    • Double-free, invalid free
    • Memory leaks (using LeakSanitizer)
  • ThreadSanitizer detects data races for multi-threaded code.

  • UndefinedBehaviourSanitizer

    detects the use of various features of C/C++ that are explicitly listed as resulting in undefined behaviour. Most notably:

    • Using misaligned or null pointer.
    • Signed integer overflow
    • Conversion to, from, or between floating-point types which would overflow the destination
    • Division by zero
    • Unreachable code
  • MemorySanitizer detects uninitialized reads.

ReactiveX

ReactiveX/RxCpp: Reactive Extensions for C++

ericniebler/range-v3: Range library for C++14/17/20, basis for C++20’s std::ranges

Reactor 3 参考指南

适用场景

Rx操作符决策树

rxjava 操作符

  1. https://reactivex.io/documentation/operators.html#categorized

  2. Operators · ReactiveX文档中文翻译

  3. https://reactivex.io/assets/operators/legend.png

    Filtering Observables

    Operators that selectively emit items from a source Observable.

    • Debounce — only emit an item from an Observable if a particular timespan has passed without it emitting another item
    • Distinct — suppress duplicate items emitted by an Observable
    • ElementAt — emit only item n emitted by an Observable
    • Filter — emit only those items from an Observable that pass a predicate test
    • First — emit only the first item, or the first item that meets a condition, from an Observable
    • IgnoreElements — do not emit any items from an Observable but mirror its termination notification
    • Last — emit only the last item emitted by an Observable
    • Sample — emit the most recent item emitted by an Observable within periodic time intervals
    • Skip — suppress the first n items emitted by an Observable
    • SkipLast — suppress the last n items emitted by an Observable
    • Take — emit only the first n items emitted by an Observable
    • TakeLast — emit only the last n items emitted by an Observable

op

CRTP

如果觉得虚函数与其重载如此痛苦竟然不能忍的话,你可以考虑 谈 C++17 里的 Builder 模式 所介绍的 CRTP 惯用法的能力,CRTP 在模板类继承体系中是个很强大的编译期多态能力。

What’s it?

CRTP 是一种 C++ 惯用法,它比 C++11 出生的早得多。在 Visual C++ 年代,ATL,WTL 以及少量的 MFC 均大规模地使用了这种技术,后来的 ProfUIS 也如此。

简单地说,CRTP 的目的在于实现编译期的多态绑定,实现方法是向基类的模板参数中传入派生类类名,于是基类就能够借助 static_cast<derived_t>(*this) 语法来获得派生类的“多态”的操作能力了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename derived_t>
class base{
  public:
  void do_sth(){
    static_cast<derived_t>(*this)->show();
  }
  void show(){hicc_debug("base::show");}
};

template <typename T>
class derived: public base<derived> {
  public:
  T _t{};
  void show(){
    hicc_debug("t: %s", hicc::to_string(_t).c_str());
  }
};

相似的旁路继承 - 一个例子

https://godbolt.org/z/oqEPsGzqe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

struct base_type {
  virtual void dump(){
    std::cout << "base_type!" << '\n';
  }
};

template<class T>
struct baseT {
  T* ThisPtr() { return static_cast<T*>(this); }
  auto& This() { return *ThisPtr(); }
  
  void call() { This().dump(); }
};

class derived: public base_type, public baseT<derived> {
  public:
  using baseT<derived>::baseT;
  
//   void dump() { 
//       base_type::dump();
//       std::cout << "derived!" << '\n';
//   }
};

int main(){
  derived d;
  d.call(); // call derived::dump()
}

有什么用处,适用场景

Range-based for loop in c++17

实际上本文内容也适用于 C++11,因为这一特性源自当时。

对于范围 for 循环的如下语句形式来说,

1
2
attr(optional) for ( init-statement(optional) range-declaration : range-expression )
loop-statement

编译器以语法糖的态度对其进行展开,展开式如同这样:

1
2
3
4
5
6
7
8
9
10
11
{
	init-statement
	auto && __range = range-expression ;
	auto __begin = begin-expr ;
	auto __end = end-expr ;
	for ( ; __begin != __end; ++__begin)
	{
		range-declaration = *__begin;
		loop-statement
	}
}

这是适用于 C++20 以上的规范性要求,但在早期(C++17 及以前),规范性要求稍稍有点宽泛:

1
2
3
4
5
6
7
8
{
	auto && __range = range-expression ;
	for (auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin)
	{
		range-declaration = *__begin;
		loop-statement
	}
}

按理说这一展开式并无不妥,只要是符合 C++11 的编译器均不应该在这一展开行为上产生歧义。但是这一表达的确不够严谨,因为早期 C,C89 等等的 for initial-statement 在 for 之后是可见的,从一以贯之的延续性角度来看,这样的表述可能误导阅读规范的人,因为随后采用了更精确无歧义的表述:C++20 开始的展开式表述适合于任何背景的阅读者,他们从此表述中能够得到完全相同的理解——而不至于因为其不同的背景而产生分歧。

这一展开式尚且包含附注来定义何者为初始化表达式,何者为 begin-expr, end-expr,等等。你可以检查其详情:

Range-based for loop (since C++11) - cppreference.com

但不管它,我们理解到,基于范围表达式的 for 循环在枚举范围对象时,要求你的对象应该实现 begin() 和 end() 这两个 iterator 方法。两者的原型为:

1
2
3
4
const_iterator begin() const;
iterator begin();
const_iterator end() const;
iterator end();

这样一来问题就转变为如何实现自己的 iterable object 了。

有的时候,你可能还需要实现 cbegin() 和 cend()

对此我的旧文章 ? 或许有所帮助。

简而言之,

c++ - How to make my custom type to work with “range-based for loops”? - Stack Overflow

🔚

标签:

分类:

更新时间:

留下评论