Post

C++ Fun With Templates

This article are notes on using template with C++ in my daily life.

Fold Expression

Example 1 (Debugging Prints)

Sometimes we may want to print debugging outputs, but we may not know the size of the inputs. For example, printing the size of a set of point clouds.

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
#include <string>
#include <iostream>

int index = 0;

template <typename T>
std::string printCloud(const T& msg)
{
  return "cloud " + std::to_string(index--) + std::string{" size "} + std::to_string(msg) + std::string{", "};
}

// Base case
std::string printClouds()
{
  return {};
}

template <typename T, typename... Args>
std::string printClouds(const T& msg, const Args&... args)
{
  // increment index
  index++;
  return printCloud(msg) + printClouds(args...);
}

int main()
{
  std::cout << "result string: " << printClouds(100, 200, 12, 11) << "\n";
  std::cout << "result string: " << printClouds(55, 67, 77) << "\n";

  return EXIT_SUCCESS;
}

From the above code, it is more complicated as a base case is needed for the recursive function to end. However, in c++17, we can make use of the fold expression to address this in a more consice manner without the need of a base case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
#include <iostream>

template <typename... Args>
std::string printClouds(const Args&... args)
{
  // Get index
  auto index = sizeof...(args);

  return ([&, args]() {
    return "cloud " + std::to_string(index--) + std::string{" size "} +
      std::to_string(args) + std::string{", "};
  }() + ...);
}

int main()
{
  std::cout << "result string: " << printClouds(100, 200, 12, 11) << "\n";
  std::cout << "result string: " << printClouds(55, 67, 77) << "\n";

  return EXIT_SUCCESS;
}

Code Analysis

Let’s compare the snippet of code and its corresponding insights when there are 7 inputs.

Code: Without Fold Expression

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
#include <string>
#include <iostream>

int index = 0;

template <typename T>
std::string printCloud(const T& msg)
{
  return "cloud " + std::to_string(index--)
  + std::string{" size "} + std::to_string(msg) + std::string{", "};
}

// Base case
std::string printClouds()
{
  return {};
}

template <typename T, typename... Args>
std::string printClouds(const T& msg, const Args&... args)
{
  // increment index
  index++;
  return printCloud(msg) + printClouds(args...);
}

int main()
{
  std::cout << "result string: " << printClouds(100, 200, 12, 11, 5, 6, 7) << "\n";
  return EXIT_SUCCESS;
}

Insights: Without Fold Expression

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// INSIGHTS
#include <string>
#include <iostream>

int index = 0;

template<typename T>
std::basic_string<char> printCloud(const T & msg)
{
  return (std::operator+(std::operator+("cloud ", std::to_string(index--)), std::basic_string<char>{" size ", std::allocator<char>()}) + std::to_string(msg)) + std::basic_string<char>{", ", std::allocator<char>()};
}

/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printCloud<int>(const int & msg)
{
  return std::operator+(std::operator+(std::operator+(std::operator+("cloud ", std::to_string(index--)), std::basic_string<char>(std::basic_string<char>(" size ", std::allocator<char>()))), std::to_string(msg)), std::basic_string<char>(std::basic_string<char>(", ", std::allocator<char>())));
}
#endif


std::basic_string<char> printClouds()
{
  return std::basic_string<char>{};
}

template<typename T, typename ... Args>
std::basic_string<char> printClouds(const T & msg, const Args &... args)
{
  index++;
  return printCloud(msg) + printClouds(args... );
}

/* First instantiated from: insights.cpp:28 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int, int, int, int, int, int>(const int & msg, const int & __args1, const int & __args2, const int & __args3, const int & __args4, const int & __args5, const int & __args6)
{
  index++;
  return std::operator+(printCloud(msg), printClouds(__args1, __args2, __args3, __args4, __args5, __args6));
}
#endif


/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int, int, int, int, int>(const int & msg, const int & __args1, const int & __args2, const int & __args3, const int & __args4, const int & __args5)
{
  index++;
  return std::operator+(printCloud(msg), printClouds(__args1, __args2, __args3, __args4, __args5));
}
#endif


/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int, int, int, int>(const int & msg, const int & __args1, const int & __args2, const int & __args3, const int & __args4)
{
  index++;
  return std::operator+(printCloud(msg), printClouds(__args1, __args2, __args3, __args4));
}
#endif


/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int, int, int>(const int & msg, const int & __args1, const int & __args2, const int & __args3)
{
  index++;
  return std::operator+(printCloud(msg), printClouds(__args1, __args2, __args3));
}
#endif


/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int, int>(const int & msg, const int & __args1, const int & __args2)
{
  index++;
  return std::operator+(printCloud(msg), printClouds(__args1, __args2));
}
#endif


/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int>(const int & msg, const int & __args1)
{
  index++;
  return std::operator+(printCloud(msg), printClouds(__args1));
}
#endif


/* First instantiated from: insights.cpp:23 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int>(const int & msg)
{
  index++;
  return std::operator+(printCloud(msg), printClouds());
}
#endif


int main()
{
  std::operator<<(std::operator<<(std::operator<<(std::cout, "result string: "), printClouds(100, 200, 12, 11, 5, 6, 7)), "\n");
  return 0;
}

Code: With Fold Expression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// RAW CODE
#include <string>
#include <iostream>

template <typename... Args>
std::string printClouds(const Args&... args)
{
  // increment index
  auto index = sizeof...(args);

  auto printCloud = [&](auto& msg) {
    return "cloud " + std::to_string(index--) + std::string{" size "} +
      std::to_string(msg) + std::string{", "};
  };
  return (... + printCloud(args));
}

int main()
{
  std::cout << "result string: " << printClouds(100, 200, 12, 11, 5, 6, 7) << "\n";

  return EXIT_SUCCESS;
}

Insights: With Fold Expression

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
62
63
64
65
66
67
68
69
// INSIGHTS
#include <string>
#include <iostream>

template<typename ... Args>
std::basic_string<char> printClouds(const Args &... args)
{
  unsigned long index = sizeof...(args);
    
  class __lambda_10_21
  {
    public: 
    template<class type_parameter_1_0>
    inline auto operator()(type_parameter_1_0 & msg) const
    {
      return (std::operator+(std::operator+("cloud ", std::to_string(index--)), std::basic_string<char>{" size ", std::allocator<char>()}) + std::to_string(msg)) + std::basic_string<char>{", ", std::allocator<char>()};
    }
    
  };
  
  auto printCloud = __lambda_10_21{};
  return (... + printCloud(args));
}

/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> printClouds<int, int, int, int, int, int, int>(const int & __args0, const int & __args1, const int & __args2, const int & __args3, const int & __args4, const int & __args5, const int & __args6)
{
  unsigned long index = 7;
    
  class __lambda_10_21
  {
    public: 
    template<class type_parameter_0_0>
    inline /*constexpr */ auto operator()(type_parameter_0_0 & msg) const
    {
      return (std::operator+(std::operator+("cloud ", std::to_string(index--)), std::basic_string<char>(std::basic_string<char>(" size ", std::allocator<char>()))) + std::to_string(msg)) + std::basic_string<char>(std::basic_string<char>(", ", std::allocator<char>()));
    }
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    inline /*constexpr */ std::basic_string<char> operator()<const int>(const int & msg) const
    {
      return std::operator+(std::operator+(std::operator+(std::operator+("cloud ", std::to_string(index--)), std::basic_string<char>(std::basic_string<char>(" size ", std::allocator<char>()))), std::to_string(msg)), std::basic_string<char>(std::basic_string<char>(", ", std::allocator<char>())));
    }
    #endif
    
    private: 
    unsigned long & index;
    
    public:
    __lambda_10_21(unsigned long & _index)
    : index{_index}
    {}
    
  };
  
  __lambda_10_21 printCloud = __lambda_10_21{index};
  return std::operator+(std::operator+(std::operator+(std::operator+(std::operator+(std::operator+(printCloud.operator()(__args0), printCloud.operator()(__args1)), printCloud.operator()(__args2)), printCloud.operator()(__args3)), printCloud.operator()(__args4)), printCloud.operator()(__args5)), printCloud.operator()(__args6));
}
#endif


int main()
{
  std::operator<<(std::operator<<(std::operator<<(std::cout, "result string: "), printClouds(100, 200, 12, 11, 5, 6, 7)), "\n");
  return 0;
}

Conclusion

From the code insights, fold expressions will only generate the relavant code for the printing. On the other hand, template without fold expression will generate all templates for the recursive used. 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0.

Using x86-64 gcc 14.1 to analysis the assembly code, only 423 lines of instructions where generated for the fold expersion version while the one without has 620.

We can conclude that fold expression method is easier to read and much more consice.

ATTENTION: One more take away, is that, a lambda expression with name is always better than one without. As the name will help compliers to optimize better. (Refer to the original fold expression example and the one in the Code Analysis)

References

This post is licensed under CC BY 4.0 by the author.

Trending Tags