Recap - Parameter Pack Fold Expressions¶

  • Jared Wyles posted in slack that he was starting to use structs with bools instead of bit flags
    • One reason is to allow the use of structured bindings
    • However, this quickly runs into a scaling issue
struct merge_layer_flags_t {
    bool use_interior_opacity = false;
    bool use_master_opacity = false;
    bool use_sheet_mask = false;
    bool use_user_mask = false;
    bool use_vector_mask = false;
    bool use_content_mask = false;
    bool use_source_ranges = false;
    bool use_destination_ranges = false;
    bool use_filter_mask = false;
};
{
    merge_layer_flags_t flags;

    flags.use_user_mask = true;
    flags.use_content_mask = true;

    auto [io, mo, sm, um, vm, cm, sr, dr, fm] = flags;

    cout << io << ", " << mo << ", " << sm << ", " << um << ", " << vm << ", " << cm
         << ", " << sr << ", " << dr << ", " << fm << endl;
}
  • Could we use an enum and use structured bindings to unpack into bools?
enum merge_layer_flags {
    use_interior_opacity = 1 << 0,
    use_master_opacity = 1 << 1,
    use_sheet_mask = 1 << 2,
    use_user_mask = 1 << 3,
    use_vector_mask = 1 << 4,
    use_content_mask = 1 << 5,
    use_source_ranges = 1 << 6,
    use_destination_ranges = 1 << 7,
    use_filter_mask = 1 << 8
};
template <auto... I, class T>
constexpr auto extract_bits_a(T x) {
    return tuple{static_cast<bool>(x & I)...};
}
{
    merge_layer_flags flags =
        static_cast<merge_layer_flags>(use_user_mask | use_content_mask);

    auto [vm, um, cm] =
        extract_bits_a<use_vector_mask, use_user_mask, use_content_mask>(flags);

    cout << vm << ", " << um << ", " << cm << endl;
}
  • This is potentially error prone
{
    auto [x] = extract_bits_a<3>(7);
    cout << x << endl;
}
  • Was the intent to extract the third bit?
    • Lower two bits?
  • ispow2() is a C++20 function but we can implement it
template <class T>
constexpr bool ispow2(T x) {
    return (x != 0) && !(x & (x - 1));
}
ispow2(3)
ispow2(4)
  • Using a fold expression in a static assert, we can check for valid mask bits
template <auto... I, class T>
constexpr auto extract_bits(T x) {
    static_assert((ispow2(I) && ...));
    return tuple{static_cast<bool>(x & I)...};
}
{
    auto [x] = extract_bits<3>(7);
    cout << x << endl;
}
input_line_22:3:5: error: static_assert failed
    static_assert((ispow2(I) && ...));
    ^              ~~~~~~~~~~~~~~~~
input_line_25:3:16: note: in instantiation of function template specialization 'extract_bits<3, int>'
      requested here
    auto [x] = extract_bits<3>(7);
               ^
{
    auto [x] = extract_bits<4>(7);
    cout << x << endl;
}

Scoped enumerations and underlying types¶

  • A scoped enumeration, enum class or enum struct, provides a strongly typed enumeration
enum class choice { none, some, all };
enum bad_choice { none, some, all };
  • An scoped enumeration defines it's own scope for names (similar to an enum declared within a class)
{
    auto pick = choice::some;
    auto bad_pick = some;
}
  • A scoped enumeration is not implicitly convertible to an integer
{
    int bad_pick = some;
    int pick = choice::some;
}
input_line_30:4:9: error: cannot initialize a variable of type 'int' with an rvalue of type 'choice'
    int pick = choice::some;
        ^      ~~~~~~~~~~~~
  • However, lack of implicit conversion can make bit fields difficult to use
enum class merge_layer {
    use_interior_opacity = 1 << 0,
    use_master_opacity = 1 << 1,
    use_sheet_mask = 1 << 2,
    use_user_mask = 1 << 3,
    use_vector_mask = 1 << 4,
    use_content_mask = 1 << 5,
    use_source_ranges = 1 << 6,
    use_destination_ranges = 1 << 7,
    use_filter_mask = 1 << 8
};
{
    auto flags = merge_layer::use_sheet_mask | merge_layer::use_vector_mask;
}
input_line_27:3:46: error: invalid operands to binary expression ('merge_layer' and 'merge_layer')
    auto flags = merge_layer::use_sheet_mask | merge_layer::use_vector_mask;
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/sean-parent/miniconda3/envs/notebook/include/c++/v1/bitset:1059:1: note: candidate template ignored: could not match 'bitset<_Size>' against 'merge_layer'
operator|(const bitset<_Size>& __x, const bitset<_Size>& __y) _NOEXCEPT
^
/Users/sean-parent/miniconda3/envs/notebook/include/c++/v1/valarray:4049:1: note: candidate template ignored: substitution failure [with _Expr1 = merge_layer, _Expr2
      = merge_layer]: no type named 'value_type' in 'merge_layer'
operator|(const _Expr1& __x, const _Expr2& __y)
^
/Users/sean-parent/miniconda3/envs/notebook/include/c++/v1/valarray:4064:1: note: candidate template ignored: substitution failure [with _Expr = merge_layer]: no type
      named 'value_type' in 'merge_layer'
operator|(const _Expr& __x, const typename _Expr::value_type& __y)
^
/Users/sean-parent/miniconda3/envs/notebook/include/c++/v1/valarray:4080:1: note: candidate template ignored: substitution failure [with _Expr = merge_layer]: no type
      named 'value_type' in 'merge_layer'
operator|(const typename _Expr::value_type& __x, const _Expr& __y)
^
  • std::underlying_type_t<> can be used to determine the type underlying any enum type
{
    using underlying = underlying_type_t<merge_layer>;

    auto flags = static_cast<merge_layer>(
        static_cast<underlying>(merge_layer::use_sheet_mask) |
        static_cast<underlying>(merge_layer::use_vector_mask));
}
  • Adobe source libraries contains <adobe/enum_ops.hpp> which allows you to enable bitwise ops
auto stlab_enable_bitmask_enum(merge_layer) -> std::true_type;

{
    auto flags = merge_layer::use_sheet_mask | merge_layer::use_vector_mask;

    auto [x] = extract_bits<merge_layer::use_sheet_mask>(flags);
    cout << x << endl;
}
  • You can specify the underlying type for any enum type
enum class small_choice : std::int16_t {
    none, some, all
};
{
    cout << sizeof(small_choice) << endl;
}
enum very_small : std::uint8_t {
    success, error
};
{
    cout << sizeof(very_small) << endl;
}
  • The underlying type of a scoped enumeration if not specified is int
  • The underlying type of a unscoped enumeration if not specified is implementation defined
    • Large enough to hold all enumerator values
    • Not larger than int unless an enumerator value cannot fit into an int
    • If empty, treated as if it had a single enumerator with value 0

Recommendations¶

  • Replace unscoped enumerations with scoped enumerations

    • Don't specify the underlying type without cause
  • Use the <adobe/enum_ops.hpp> (which may become <stlab/enum_ops.hpp> soon) for

    • Types that represent a arithmetic type
    • Types that represent bit fields

Homework¶

  • Replace an unscoped enumeration with a scoped enumeration in your project
    • Did it improve the appearance of the code or clutter it?
    • Did it catch any errors?

Type Aliases¶

  • A type alias is a new syntax for typedef declarations
namespace cpp98 {

typedef int some_type;

}
using some_type = int;
  • The new syntax makes complex aliases easier to read and write
namespace cpp98 {

typedef int (*some_func)(int);

}
using some_func = int (*)(int);
  • Unlike typedef, a type alias can be declared as a template
template <class T>
using func_ptr = T (*)(T);
{
    func_ptr<double> f = [](double x){ return x * x; };

    cout << f(10);
}
  • A template type alias is useful to define an alias to a dependent type
namespace cpp98 {

template <class I>
auto distance(I f, I l) -> typename iterator_traits<I>::difference_type;

}
template <class I>
using difference_t = typename iterator_traits<I>::difference_type;
template <class I>
auto distance(I f, I l) -> difference_t<I>;

Recommendations¶

  • Prefer type aliases to typedefs
  • Use template type aliases as type functions to simplify complex type expressions

Homework¶

  • Replace some typedefs in your project with type aliases
  • Find an instance of typename used for a dependent type and replace it with template type alias
    • Hint use the regular expression [^,<] typename to find an instance

Templates Variables¶

  • C++14 added template variables
    • A non-const template variable will only have one instance across translation units
      • i.e. implicitly inline
    • However, a const (or constexpr) template variable is implicitly static, one instance per translation unit
      • But can be declared inline
namespace {

template <class T>
inline constexpr T max_value = std::numeric_limits<T>::max();

} // namespace
{
    auto x = max_value<int>;

    cout << x << endl;
}

Recommendations¶

  • There are minor advantages to template variables over template static members and template functions
    • Use as needed (rarely)

Homework¶

  • None

Extern Templates¶

  • An explicit instantiation declaration of a template tell the compiler that an explicit instantiation definition exists in exactly one compilation unit
// header.hpp

namespace library {

template <class T>
class my_wizzy_type {
    void member_function();
    // ...
};

extern template class my_wizzy_type<int>;

} // namespace library
// code.cpp

#include "header.hpp"
namespace library {

template <class T>
void my_wizzy_type<T>::member_function() {
    //...
}

template class my_wizzy_type<int>;

} // namespace library

Recommendations¶

  • If you currently rely on hacks to force instantiation in a translation unit, at least use this as a supported mechanism
  • Potentially useful for controlling instantiation for DLLs
    • But still prefer DLLs be avoided
  • May speed compilation times and allow more separation of interface from implementation
    • Measure

Homework¶

  • None (unless you are currently doing this with a hack, in which case, fix it!)

Variadic Macros¶

  • C99 added variadic macros, picked up by C++ in C++11
    • __VA_ARGS__ holds argument list
    • __VA_OPT__(content) can be used in the replacement (C++20)
      • If __VA_ARGS__ is not empty __VA_OPT__(content) is replaced with content
      • Otherwise __VA_OPT__(content) expands to nothing
#define ARRAY(...) \
    int array[] = { __VA_ARGS__ }
{
    ARRAY(5, 3);
    for (const auto& e : array) cout << e << endl;
}
  • Stringizing __VA_ARGS__ quotes the entire replacement
#define SHOW(...) \
    #__VA_ARGS__
SHOW(10, 42.5, x)

Recommendations¶

  • Macros are still best avoided
    • File this in your bag of tricks...

Homework¶

  • None

Another Detour¶

  • After break we are going to spend a few (4?) courses on testing theory and writing unit tests
    • All white-box QE are invited and encouraged to attend
      • Black-box QE may also find it interesting
    • Very valuable for devs as well
  • This section came about after reviewing several candidates results of a take-home project to write a unit test for std::vector<>

Section Outline¶

  • Why test?
  • How is meaning ascribed to software?
    • Axioms and Equational Reasoning
  • Design by Contract
  • Concepts and models
  • Quantifying, measuring, and testing performance
  • Requirements of the Basic Interface
  • What is not testable, and why
  • My goal is everyone who attends (and does the homework) should be able to write an A+ unit test for std::vector<>