Today we’ll cover a couple of more minor features that don’t have C# equivalents: fold expressions and elaborated type specifiers. Though they are small, they can be quite useful!

Table of Contents

Fold Expressions

Fold expressions, available since C++17, allow us to apply a binary operator to all the parameters in a template’s parameter pack. For a simple example, say we want to add up some integers:

// Template parameters are a pack of integers
template<int... Vals>
// Apply the + operator to the Vals pack
int SumOfAll = (Vals + ...);
 
// Instantiate the template with four integers in the Vals pack
DebugLog(SumOfAll<1, 2, 3, 4>); // 10

The “fold expression” is the (Vals + ...) part. Parentheses are required here, unlike most expressions. We name the parameter pack (Vals), name the binary operator (+), and add ... to indicate that we want to fold that operator over the pack.

When the template is instantiated, the compiler converts the fold expression into a series of binary operators:

// Expanded version of the template parameter pack
template<int Val1, int Val2, int Val3, int Val4>
// Expanded version of the fold expression
int SumOfAll = Val1 + (Val2 + (Val3 + Val4));

This kind of fold expression is called a “unary right fold.” This means that the rightmost arguments have the operator applied to them fist.

In order to reverse this and apply the operator to the leftmost arguments first, we use a “unary left fold” like this:

template<int... Vals>
int SumOfAll = (... + Vals); // Swapped "..." and "Vals"
 
DebugLog(SumOfAll<1, 2, 3, 4>); // 10

When instantiated, the compiler produces the equivalent of this:

template<int Val1, int Val2, int Val3, int Val4>
int SumOfAll = ((Val1 + Val2) + Val3) + Val4;

The choice of a left or right fold doesn’t really matter when we’re just adding integers, but it will surely matter with other types and other operators.

If the parameter pack happens to be empty, only three binary operators are allowed. First, we can use && to evaluate to true:

template<bool... Vals>
bool AndAll = (... && Vals);
 
DebugLog(AndAll<false, false>); // false
DebugLog(AndAll<false, true>); // false
DebugLog(AndAll<true, false>); // false
DebugLog(AndAll<true, true>); // true
DebugLog(AndAll<>); // true

Second, we can use || to evaluate to false:

template<bool... Vals>
bool OrAll = (... || Vals);
 
DebugLog(OrAll<false, false>); // false
DebugLog(OrAll<false, true>); // true
DebugLog(OrAll<true, false>); // true
DebugLog(OrAll<true, true>); // true
DebugLog(OrAll<>); // false

And third, which is by far the least common use case, the , operator will evaluate to void():

template<bool... Vals>
void Goo()
{
    return (... , Vals); // Equivalent to "return void();"
}
 
// OK
Goo();

Now that we’ve seen the “unary” fold expressions, let’s look at the “binary” ones. To make these, we add the same binary operator after the ... then an additional value:

template<int... Vals>
// Add the operator (+) then an additional value (1) after the unary fold
int SumOfAllPlusOne = (Vals + ... + 1);
 
DebugLog(SumOfAllPlusOne<1, 2, 3, 4>); // 11

Here we’ve converted the unary fold expression (Vals + ...) into a binary one by adding + 1 to the end of it. This adds another value in addition to the values in the parameter pack. Since this was a “binary right fold” the parentheses will be added on the rightmost values first:

template<int Val1, int Val2, int Val3, int Val4>
int SumOfAll = 1 + (Val1 + (Val2 + (Val3 + Val4)));

The “binary left fold” version just has the additional value on the left:

template<int... Vals>
int SumOfAllPlusOne = (1 + ... + Vals);

When instantiated with four values in the parameter pack, it’ll look like this:

template<int Val1, int Val2, int Val3, int Val4>
int SumOfAll = (((1 + Val1) + Val2) + Val3) + Val4;

Regardless of which kind of fold expression we write, we’re allowed to use any of these binary operators:

  • +
  • -
  • *
  • /
  • %
  • ^
  • &
  • |
  • =
  • <
  • >
  • <<
  • >>
  • +=
  • -=
  • *=
  • /=
  • %=
  • ^=
  • &=
  • |=
  • <<=
  • >>=
  • ==
  • !=
  • <=
  • >=
  • &&
  • ||
  • ,
  • .*
  • ->*
Elaborated Type Specifiers

We’ve seen before that C code requires us to use struct Player instead of just Player as the type name of the Player struct:

// C code
 
struct Player
{
    int Health;
    int Speed;
};
 
struct Player p; // C requires "struct" prefix
p.Health = 100;
p.Speed = 10;
DebugLog(p.Health, p.Speed); // 100, 10

That’s usually not necessary in C++. However, there is an edge case where we have both a class and a variable with the same name. Using the name refers to the variable, so we’re unable to use the type anymore:

// A class
struct Player
{
};
 
// A variable with the same name as the class
int Player = 123;
 
// Compiler error: Player is not a type
// This is because "Player" refers to the variable, not the class
Player p;

To get around this, we can use the C-style struct Player to explicitly state that we’re referring to the struct, not the variable. This is called an “elaborated type specifier” since we are elaborating on the Player type:

// Elaborated type specifier
// OK: refers to the Player struct, not the Player variable
struct Player p;

Since struct and class are very similar, we can use them interchangeably in our elaborated type specifiers:

// Elaborated type specifier using "class" when Player is a "struct"
class Player p;

Unions are not interchangeable:

// A union
union IntFloat
{
    int32_t Int;
    float Float;
};
 
// A variable with the same name as the union
bool IntFloat = true;
 
// Compiler error: IntFloat is not a type
// This is because "IntFloat" refers to the variable, not the union
IntFloat u;
 
// Elaborated type specifier
// Compiler error: IntFloat is a union, not a class or struct
class IntFloat u;
 
// Elaborated type specifier
// OK: refers to the IntFloat union, not the IntFloat variable
union IntFloat u;

Enumerations are also their own kind of entity and need to be elaborated with enum:

// An enumeration
enum DamageType
{
    Physical,
    Water,
    Fire,
    Magic,
};
 
// A variable with the same name as the enum
float DamageType = 3.14f;
 
// Elaborated type specifier
// Compiler error: DamageType is an enum, not a class or struct
class DamageType d;
 
// Elaborated type specifier
// OK: refers to the DamageType enum, not the DamageType variable
enum DamageType d;

Plain enum can be used with a scoped enumeration but enum class or enum struct can’t be used with unscoped enumerations and must be used with scoped enumerations:

enum class Scoped
{
};
 
enum Unscoped
{
};
 
enum Scoped e1; // OK
enum Unscoped e2; // OK
enum class Scoped e3; // OK
enum class Unscoped e4; // Compiler error: can't use scoped with unscoped enum
enum struct Scoped e5; // OK
enum struct Unscoped e6; // Compiler error: can't use scoped with unscoped enum

Regardless of the type, we can also refer to its location in a namespace using the scope resolution operator:

namespace Gameplay
{
    enum DamageType
    {
        Physical,
        Water,
        Fire,
        Magic,
    };
 
 
    float DamageType = 3.14f;
}
 
// Elaborated type specifier using scope resolution operator
enum Gameplay::DamageType d;

The same goes for classes’ member types:

struct Gameplay
{
    // Member type of the class
    enum DamageType
    {
        Physical,
        Water,
        Fire,
        Magic,
    };
 
    // Member variable of the class
    constexpr static float DamageType = 3.14f;
};
 
// Elaborated type specifier referring to class member type
enum Gameplay::DamageType d;
Conclusion

Fold expressions provide a way for us to cleanly apply binary operators to templates’ parameter packs. Without them, we’d need to resort to alternatives such as recursively instantiating templates and using specialization to stop the recursion. That’s much less readable and much slower to compile as many templates would need to be instantiated and then then thrown away. We get our choice of unary or binary and left or right folds so we can control how the binary operator is applied to the values of the parameter pack. Since C# doesn’t have variadic templates, it also doesn’t have fold expressions.

Elaborated type specifiers are a minor feature that provides us a workaround in the case where we have types with the same name as variables. We can refer to these types explicitly to change the default meaning of the name shared between the type and the variable. This is rarely necessary, but a nice tool to have when the situation arises. C# doesn’t allow a type to have the same name as a variable, so there’s no equivalent to this in that language.