Post

C++17 Structured Binding Tutorial

Summary

Structured binding is a feature released in C++17 that easily allows a function to return multiple values. This is a quick summary of the various ways to use it, including in a device function. Note that in C++17 structured binding returns cannot be const, this was added in C++17.

Code

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
// Demonstration of C++17 structured binding in host and device code. Compile with
// `nvcc -std=c++17 structured-binding.cu` on CUDA on
// 'hipcc -g -std=c++17 structured-binding.cu` on HIP

#include <iostream>
#include <tuple>
#include <vector>

#ifdef __HIP__
#include<hip/hip_runtime.h>
#endif //HIP

// =============================================================================
// The classic method of returning multiple values is to pass them to the
// function by reference and set them within the function. This can be slow and
// unclear. C++17 structured binding attempts to address this and gives you
// several options in implementation.
// More details on the reasons that structured binding is often faster can be
// found [here](https://www.nexthink.com/blog/c17-function/)
// =============================================================================

// =============================================================================
// Returning a std::pair is fast since it doesn't use the stack if the return
// is <= 128 bits. Requires C++17 and can't be overloaded.
// Functions that return std::pair or std::tuple can either be of type `auto` or
// have their type explicitely declared. Both are shown here
auto pairReturn()
{
    return std::make_pair(7,8);
}
// =============================================================================

// =============================================================================
// Uses stack. Slower than returning a local struct or a std::pair but easier to
// add values to
std::tuple<int, int, double> tupleReturn()
{
    return {3,4,17.4};
}
// =============================================================================

// =============================================================================
// Returning a local struct is fast since it doesn't use the stack if the return
// is <= 128 bits. Requires C++17 and can't be overloaded
auto __device__ __host__ localStructReturn()
{
    struct returnStruct
    {
        int ret1, ret2;
    };

    return returnStruct{5,6};
}
// =============================================================================

// =============================================================================
__global__ void printValues()
{
    if (threadIdx.x == 0)
    {
        auto [devA, devB] = localStructReturn();
        printf("devA = %i\ndevB = %i\n", devA, devB);
    }
}
// =============================================================================

int main()
{
    // Simple structured binding from array. Does not work for std::vector since
    // it's details are not known at compile time. It does work with std::array
    // though
    int a[2] = {1,2};
    auto [b,c] = a;

    std::cout << b << std::endl;
    std::cout << c << std::endl;

    auto [d,e,dbl] = tupleReturn();

    std::cout << d << std::endl;
    std::cout << e << std::endl;
    std::cout << dbl << std::endl;

    auto [f,g] = localStructReturn();

    std::cout << f << std::endl;
    std::cout << g << std::endl;

    auto [h,i] = pairReturn();

    std::cout << h << std::endl;
    std::cout << i << std::endl;

    #ifdef __HIP__
        hipLaunchKernelGGL(printValues, 1,1,0,0);
        auto ignore = hipDeviceSynchronize();
    #else // CUDA
        printValues<<<1,1>>>();
        cudaDeviceSynchronize();
    #endif //HIP


    return 0;
}
This post is licensed under CC BY 4.0 by the author.