Thursday, September 24, 2020

Guest Post On Fluent C++

Guest Post On Fluent C++

On September 11th, 2020, I had a guest post published on Jonathan Bocarra’s blog, Fluent C++. This is one of the C++ blogs I read regularly so I was very excited to have something published there.

The post is titled “Replacing CRTP Static Polymorphism With Concepts” and in it I iteratively update legacy CRTP code with C++20 concepts. This is important because you may not be able to switch your whole code base to a concept based design all at once. Even so, you may find that a partial refactor will allow you to take advantage of the expressiveness and improved compile time error messages provided by concepts.

You can check out the post here.

Tuesday, April 7, 2020

Managing Class Options

Managing Class Options

I’ve found myself recently needing to handle passing and managing options across all of the components of our application.
I wanted all components to have their own options that could be managed externally.

Approach

I nest the options struct in the class and let it be qualified by the class name.

class MyComponent {
public:
  struct Options {
    struct {
      bool some_option{false};
      /* some other options */
    } MyComponent;
  };
  
  MyComponent() {}
  MyComponent(std::shared_ptr<Options>& options) :
      m_options{options} {}
  /* ... */
private:
  std::shared_ptr<Options> m_options{new Options};
};

We will devise a way to aggregate options and get a specific slice of options from it.

template <typename... TOptions>
struct OptionsAggregate : public TOptions... {
  using Type = OptionsAggregate<TOptions...>;
  template <typename TOptionType>
  auto Get() -> TOptionType& {
    static_assert(std::is_base_of_v<TOptionType, Type>,
      "Requested options are not a base of this aggregate");
    return static_cast<TOptionType&>(*this);
  }
};

So this variadic class template will take several options classes and inherit from them. It also has a Get method to get a slice of the aggregate.

We will have a ComponentManager that manages all of the components and their options.

template <typename TOptions>
class ComponentManager {
public:
  ComponentManager() {}
  ComponentManager(std::shared_ptr<MyComponent> component,
                   std::shared_ptr<Options> options) :
     m_options{std::move(options)} {}
  void Run();
private:
  std::shared_ptr<TOptions> m_options{new TOptions};
};

We can then use all of these things like the following:

struct GeneralOptions {
  bool debug{false};
};

using ComponentOptions = OptionsAggregate<GeneralOptions, MyComponent::Options>;
auto options = std::make_shared<ComponentOptions>();

options->Get<GeneralOptions>().debug = true;
/* set other options */

// the options that component can see are only a slice of the overall options
auto component = std::make_shared<MyComponent>(options);

ComponentManager manager{component, options};

A problem I discovered with this method appears when using the slicing syntax from ComponentManager methods which results in very unfortunate syntax. Imagine component manager had a method called Run:

template<typename TOptions>
void ComponentManager<TOptions>::Run() {
  auto& general_options = m_options->template Get<GeneralOptions>();
  /* use general_options */
}

This occurs because m_options depends on a template parameter as well as OptionsAggregate::Get.

Check Compiler Explorer for a full example.

Friday, February 14, 2020

Generic Visitor Builder

Generic Visitor Builder

Recently, I’ve been implementing an internal scene graph data structure and I want to show my solution for building generic scene graph visitors.

Lets consider a basis type for our tree.

class base_node {
public:
  base_node() : m_name{"default"} {}
  base_node(std::string name) : m_name{std::move(name)} {}

  [[nodiscard]] auto& name() { return m_name; }
  [[nodiscard]] auto& name() const { return m_name; }
  [[nodiscard]] auto& children() { return m_children; }
  [[nodiscard]] auto& children() const { return m_children; }

private:
  std::string m_name;
  std::vector<base_node> m_children;
};

We can create a new scene graph like this:

  auto layer_1 = base_node{"parent"};
  
  auto& layer_2 = layer_1.children();
  layer_2 = {{"child_1"},{"child_2"},{"child_3"},{"child_4"},{"child_5"}};

  auto& layer_3 = layer_2[1].children();
  layer_3 = {{"child_6"},{"child_7"},{"child_8"},{"child_9"},{"child_10"}};

It has a name and a vector of base_nodes to hold its children.

So now lets say you want to apply some function to the whole scene graph; lets say you want to print the hierarchy.

We could write a visitor functor which takes a base_node and recursively calls itself with all of its children printing the name of each node. For this example, we will visit in a preorder fashion.

We will also add some state to make it look pretty.

struct print_hierarchy {
  void operator()(base_node& parent) {
    auto put_indent = [count = m_level] (const char indent_char) mutable {
      while(count--) std::cout << indent_char;
    };
    auto increase_indent = [&] () {
      m_level += 2;
    };
    auto decrease_indent = [&] () {
      m_level -= 2;
    };

    put_indent('-');

    std::cout << parent.name() << '\n';

    increase_indent();
    for(auto& node: parent.children()) {
      (*this)(node);
    }
    decrease_indent();
  }
  
private:
  size_t m_level{0};
};

If we call this on our hierarchy, we get:

parent
--child_1
--child_2
----child_6
----child_7
----child_8
----child_9
----child_10
--child_3
--child_4
--child_5

We can make this more generic by replacing the cout with a call to some callable.

We need to pass a Callable template argument to our visitor. We will also rename it to hierarchy_visitor

struct hierarchy_visitor {
  template<class TCallable>
  void operator()(base_node& parent, TCallable callable) {
    auto put_indent = [count = m_level] (const char indent_char) mutable {
      while(count--) std::cout << indent_char;
    };
    auto increase_indent = [&] () {
      m_level += 2;
    };
    auto decrease_indent = [&] () {
      m_level -= 2;
    };

    put_indent('-');

    callable(parent);

    increase_indent();
    for(auto& node: parent.children()) {
      (*this)(node, callable);
    }
    decrease_indent();
  }
private:
  size_t m_level{0};
};

We can now do this:

auto print_node = [](base_node& parent) {
  std::cout << parent.name() << '\n';
};

auto visitor = hierarchy_visitor();
visitor(layer_1, print_node);

But now we have a new problem. We have formatting hard coded into our visitor. It isn’t as generic as it could be.

We want to refactor out the formatting code. We can move put_indent to the callable and make the callable accept a layer parameter that tells the current level in the hierarchy visitor. It makes since for a hierarchy visitor to know its current level so level will remain inside the visitor. We will also rename the lambdas to something that sounds more generic.

struct hierarchy_visitor {
  template <class TCallable>
  void operator()(base_node& parent, TCallable callable) {
    auto increase_level = [&]() { m_level += 1; };
    auto decrease_level = [&]() { m_level -= 1; };

    callable(parent, m_level);

    increase_level();
    for (auto& node : parent.children()) {
      (*this)(node, callable);
    }
    decrease_level();
  }

 private:
  size_t m_level{0};
};

auto print_node = [indent_char = '-', indent_width = 2](base_node& parent, auto level) {
  auto put_indent = [&indent_width, level](auto indent_char) mutable {
    level *= indent_width;
    while (level--) std::cout << indent_char;
  };

  put_indent(indent_char);
  std::cout << parent.name() << '\n';
};

What if we don’t want to pass our callable in every recursive call? We can allow our hierarchy visitor to store it as a private member.

template<class TCallable>
struct hierarchy_visitor {
  void operator()(base_node& parent) {
    auto increase_level = [&]() { m_level += 1; };
    auto decrease_level = [&]() { m_level -= 1; };

    m_callable(parent, m_level);

    increase_level();
    for (auto& node : parent.children()) {
      (*this)(node, callable);
    }
    decrease_level();
  }

 private:
  TCallable m_callable;
  size_t m_level{0};
};

But now we have an issue where we cant determine the type of a lambda to instantiate an instance of hierarchy_visitor. We will have to give hierarchy_visitor a constructor which takes a TCallable.

But even this is not enough. Since
We will have to create a builder.

template <typename Callable>
hierarchy_visitor<Callable> build_hierarchy_visitor(Callable callable) {
  return {callable};
}

Now we can build a hierarchy_visitor but what about other types of visitors?

We can use some lambda magic with variable templates to accept a generic TCallable and TVisitor.

template <template <class> class TVisitor>
auto build_visitor =
    [](auto callable) -> TVisitor<std::decay_t<decltype(callable)>> {
  return {callable};
};

Unfortunately this doesn’t work in MSVC without -std:c++latest so we’ll have to try something else for the most portable code.

template <template <class> class TVisitor, class TCallable>
TVisitor<TCallable> build_visitor(TCallable callable) {
  return {callable};
}

We can then create new visitors and call them like this:

  auto visitor = build_visitor<hierarchy_visitor>(print_node);
  visitor(layer_1);

I tested this one on Clang 9, GCC 9.2, and MSVC 19.24 and it works fine.

Here is an alternative which may be more flexible and also works on all platforms:

template <template <class> class TVisitor>
struct build_visitor_impl {
  template <typename TCallable>
  constexpr auto operator()(TCallable callable) const
      -> TVisitor<decltype(callable)> {
    return {callable};
  }
};

template <template <class> class TVisitor>
inline constexpr auto build_visitor = build_visitor_impl<TVisitor>{};

View the full code on Compiler Explorer.

Thanks for reading!