• Keine Ergebnisse gefunden

10.2 adapter type 119

1 namespace Utils {

2 template <typename T> class Point {

3 T x, y, z;

4 };

5 template <typename T>

6 T distance(const Point<T> &a, const Point<T> &b) {

7 return std::sqrt(a * a + b * b + c * c);

8 }

9 }

10

11 using Point = Utils::Point<float>;

12

13 template <typename P>

14 float f(const P &p1, const P &p2) {

15 return min_reduction(distance(p1, p2));

16 }

Listing 10.4: Non-member function relying on argument-dependent name lookup.

1 template <typename Base> class Adapter : public Base {};

2

3 template <template <typename...> class C, typename... Ts>

4 struct ReplaceTypes<C<Ts...>> {

5 using type = Adapter<C<typename ReplaceTypes<Ts>::type...>>;

6 };

7

8 template <typename T> using simdize = typename ReplaceTypes<T>::type;

Listing 10.5:The required code for adding an adapter class on top of the simdized type.

10.2.1 vector width

Once we have an adapter class A deriving from S we can easily implement the constant expression that denotes the SIMD width (cf. item 9 above). This requires astatic constexprmember function in theAdapter class template. The value of this member requires a solution for the reconciliation of the vector widths (cf.

item 1 above).

To determine the common vector width, a heuristic needs to be applied since there is no obvious intended width if different arithmetic types are used in the template arguments ofD. A simple and effective strategy is the use of the natural vector width of the first Vector<T> that is substituted. This strategy allows an implementation that does a single pass over the template arguments.

The definition of such asimdize<T> implementation is shown in Listings 10.6 and 10.7. The code in Listing 10.6 extends the previoussimdize<T> implementa-tion with a new template parameterN:

• The new parameterN tosimdize andReplaceTypes selects whether Vec-tor<T> orSimdArray<T, N>is used (cf. line 22).

• TheAdapter class uses the template parameterN to implement thesize() function.

1 template <typename T, std::size_t N,

2 bool = (std::is_same<T, short>::value ||

3 std::is_same<T, unsigned short>::value ||

4 std::is_same<T, int>::value ||

5 std::is_same<T, unsigned int>::value ||

6 std::is_same<T, float>::value ||

7 std::is_same<T, double>::value)>

8 struct ReplaceTypes;

9

10 template <typename T, std::size_t N = 0>

11 using simdize = typename ReplaceTypes<T, N>::type;

12

13 // specialization for non-simdizable types

14 template <typename T, std::size_t N>

15 struct ReplaceTypes<T, N, false> {

16 typedef T type;

17 };

18

19 // specialization for simdizable arithmetic types

20 template <typename T, std::size_t N>

21 struct ReplaceTypes<T, N, true>

22 : public std::conditional<(N == 0 || Vector<T>::size() == N),

23 Vector<T>, SimdArray<T, N>> {};

24

25 template <typename Base, // A class template instance with vectorized

26 // template arguments

27 typename Scalar, // The same as Base except with

28 // non-vectorized template arguments

29 std::size_t N>

30 class Adapter : public Base {

31 public:

32 typedef Scalar ScalarType;

33 static constexpr std::size_t size() { return N; }

34 };

Listing 10.6:ExtendingReplaceTypesto use an equalSIMDwidth for all replaced types.

10.2.2 recursive type substitution

The code in Listing 10.7 is used to recurse over the template arguments ofD. The partial specialization ofReplaceTypeson line 76 uses the template class Substi-tuteOneByOneto substitute the template argument types. The result of this substi-tution is returned as a structure type with one static member for the determined vector width and a template alias that can instantiate a given class template with the substituted parameter pack. If the type substitution yields the same types (line 80) thenReplaceTypes defines its member typetypeto the original template ar-gument that it was instantiated with (i.e. it does not use theAdapterclass). Other-wise, the substituted type contains a vector type and therefore needs to create an instance of theAdapterclass template which derives from it (line 82).

TheSubstituteOneByOne class template needs two parameter packs to iterate over the template arguments. One parameter pack stores the types that are already

10.2 adapter type 121

35 // Typelist for multiple parameter packs in one class template

36 template <typename... Ts> struct Typelist;

37

38 // Try substituting one type after another - the first one that

39 // succeeds sets N (if it was 0)

40 template <std::size_t N, typename Replaced, typename... Remaining>

41 struct SubstituteOneByOne;

42 template <std::size_t N, typename... Replaced, typename T,

43 typename... Remaining>

44 struct SubstituteOneByOne<N, Typelist<Replaced...>, T, Remaining...> {

45 private:

46 // U::size() or 0

47 template <typename U, std::size_t M = U::size()>

48 static std::integral_constant<std::size_t, M> size_or_0(int);

49 template <typename U>

50 static std::integral_constant<std::size_t, 0> size_or_0(...);

51 typedef simdize<T, N> V;

52 static constexpr auto NN =

53 N != 0 ? N : decltype(size_or_0<V>(int()))::value;

54

55 public:

56 using type = typename SubstituteOneByOne<

57 NN, Typelist<Replaced..., V>, Remaining...>::type;

58 };

59

60 // specialization for ending the recursion and setting the return type

61 template <std::size_t N_, typename... Replaced>

62 struct SubstituteOneByOne<N_, Typelist<Replaced...>> {

63 // Return type for returning the vector width and list of

64 // substituted types

65 struct type {

66 static constexpr auto N = N_;

67 template <template <typename...> class C>

68 using Substituted = C<Replaced...>;

69 };

70 };

71

72 // specialization for class templates where all template arguments

73 // need to be substituted

74 template <template <typename...> class C, typename... Ts,

75 std::size_t N>

76 struct ReplaceTypes<C<Ts...>, N, false> {

77 typedef typename SubstituteOneByOne<N, Typelist<>, Ts...>::type tmp;

78 typedef typename tmp::template Substituted<C> Substituted;

79 static constexpr auto NN = tmp::N;

80 typedef typename std::conditional<

81 std::is_same<C<Ts...>, Substituted>::value, C<Ts...>,

82 Adapter<Substituted, C<Ts...>, NN>>::type type;

83 };

Listing 10.7:ExtendingReplaceTypesto use an equalSIMDwidth for all replaced types.

substituted and the other pack stores the types that still need to be processed.2 The typeTbetween the two lists signifies the type that is being processed by the currentSubstituteOneByOne instance. This type is transformed via a simdize<

T, N> expression (line 51) and thus recurses if the typeT is a template instance.

In case the N parameter to SubstituteOneByOne was zero, a successful type re-placement then has to define the vector width for all remaining type rere-placements.

Line 53 therefore checks forN == 0, in which case it tries to callV::size() as a constant expression. If this were ill-formed then a zero is returned through the SFINAE mechanism in thesize_or_0 member function overloads (lines 47–50).

WithTprocessed, the substitution process recurses via defining the next Substi-tuteOneByOneinstance (line 56) as the member typetype. Line 62 finally ends the recursion ofSubstituteOneByOnewhen theRemainingparameter pack is empty.

ThisSubstituteOneByOne instance then defines the return typetype as a struct with the information that needs to be passed back toReplaceTypes, which cap-tures this type on line 77.

10.2.3 broadcast constructor

With the adapter classAderiving fromS, it is possible to add a constructor to the generated type, which allows the conversion from an object of typeDtoS. This is analogous to the broadcast constructor ofVector<T>. It converts a scalar object to an object of the vectorized type by broadcasting the scalar members into all entries of the vector members. The constructor definition is shown in line 12 of Listing 10.8.

Note that the interface ensures the existence of thestd::tuple interface for the constructor parameter typeU. This is necessary to access the data members of the constructor argument, as shown in line 19.

10.2.4 inherited constructors

The constructors ofC(which are still valid and useful for the template instantiation S) can be inherited byA, so that no functionality is lost (line 2 of Listing 10.8).3For completenessAneeds a defaulted default constructor (line 5), which will be usable only ifShas a default constructor.

2 C++cannot distinguish where one pack ends and the next starts. Therefore, the first pack is passed via theTypelistclass template.

3 Constructor inheritance is not the right solution for aggregates. In this case no constructor would be inherited even though the base type can be instantiated via brace initialization. Alternatively, a ge-neric constructor that forwards all arguments can be used. However, such a forwarding constructor requires complicated logic to determine whether to use parenthesis, braces, or double braces (such as forstd::array) for the call to the base constructor.