7
\$\begingroup\$

In modern C++, many classes accept a policy type as a template parameter. The author of the class does not know in advance what kind of policy its class will be instantiated with. If the policy class is empty, it would be desirable to inherit from it so to leverage the empty base optimization (EBO). On the other hand, not every type can be inherited from. Counter-examples include unions, classes declared as final or non-class types like int.

I wrote the following utility policy_base_t<T> that allows library authors to make use of EBO when it is possible to inherit from T and fall back to delegation otherwise. It will always be possible to inherit from policy_base_t<T> itself. A reference to the policy object may be obtained via the (protected) policy member function.

#ifndef META_HXX
#define META_HXX

#include <type_traits>
#include <utility>


namespace meta
{

  namespace detail
  {

    template <typename PolicyT>
    class deriving_policy : private PolicyT
    {

    protected:

      template <typename... ArgTs>
      deriving_policy(ArgTs&&... args)
        noexcept(noexcept(PolicyT (std::declval<ArgTs>()...))) :
        PolicyT (std::forward<ArgTs>(args)...)
      {
      }

      PolicyT&
      policy() noexcept
      {
        return *this;
      }

      const PolicyT&
      policy() const noexcept
      {
        return *this;
      }

    };


    template <typename PolicyT>
    class delegating_policy
    {

    private:

      PolicyT policy_;  // not initialized

    protected:

      template <typename... ArgTs>
      delegating_policy(ArgTs&&... args)
        noexcept(noexcept(PolicyT (std::declval<ArgTs>()...))) :
        policy_ (std::forward<ArgTs>(args)...)
      {
      }

      PolicyT&
      policy() noexcept
      {
        return this->policy_;
      }

      const PolicyT&
      policy() const noexcept
      {
        return this->policy_;
      }

    };

  }  // namespace detail

  template <typename T>
  using policy_base_t = typename std::conditional
  <
    (std::is_class<T>::value && !std::is_final<T>::value),
    detail::deriving_policy<T>,
    detail::delegating_policy<T>
  >::type;

}  // namespace meta


#endif  // #ifndef META_HXX

I wrote the following tests. (I'm not asking for a code review of the tests; just showing them so you can see the intended usage. I'm aware that the static_assertions are not mandated by the standard to pass and thus non-portable but they do pass on current GCC and Clang.)

#include "meta.hxx"

#include <iostream>
#include <string>


namespace /* anonymous */
{

  struct empty
  {
  };

  std::ostream&
  operator<<(std::ostream& os, const empty&)
  {
    return os << "empty";
  }

  struct empty_final final
  {
  };

  std::ostream&
  operator<<(std::ostream& os, const empty_final&)
  {
    return os << "empty & final";
  }

  struct non_empty
  {
    int a;
  };

  std::ostream&
  operator<<(std::ostream& os, const non_empty& o)
  {
    return os << "non-empty [a = " << o.a << "]";
  }

  struct non_empty_final final
  {
    int a;
  };

  std::ostream&
  operator<<(std::ostream& os, const non_empty_final& o)
  {
    return os << "non-empty & final [a = " << o.a << "]";
  }

  union integer
  {
    signed int s;
    unsigned int u;
  };

  std::ostream&
  operator<<(std::ostream& os, const integer& o)
  {
    return os << "a union [signed = " << o.s << ", unsigned = " << o.u << "]";
  }

  union nothing
  {
  };

  std::ostream&
  operator<<(std::ostream& os, const nothing&)
  {
    return os << "an empty union";
  }

  template <typename PolicyT>
  class test : private meta::policy_base_t<PolicyT>
  {

    int dummy;

  public:

    template <typename... ArgTs>
    test(ArgTs&&... args) : meta::policy_base_t<PolicyT> {std::forward<ArgTs>(args)...}
    {
    }

    void
    show_policy()
    {
      std::cout << "my policy is: " << this->policy() << '\n';
    }

  };

  struct float_and_int
  {
    float f;
    int i;
  };

  struct string_and_int
  {
    std::string s;
    int i;
  };

}  // namespace /* anonymous */


int
main()
{
  test<empty> t1 {};
  t1.show_policy();
  static_assert(sizeof(t1) == sizeof(int), "expected EBO");

  test<empty_final> t2 {};
  t2.show_policy();
  static_assert(sizeof(t2) == 2 * sizeof(int), "unexpected EBO");

  test<non_empty> t3 {non_empty {42}};
  t3.show_policy();
  static_assert(sizeof(t3) == 2 * sizeof(int), "weired");

  test<non_empty_final> t4 {non_empty_final {42}};
  t4.show_policy();
  static_assert(sizeof(t4) == 2 * sizeof(int), "weired");

  test<integer> t5 {integer {-42}};
  t5.show_policy();
  static_assert(sizeof(t5) == 2 * sizeof(int), "how come?");

  test<nothing> t6 {};
  t6.show_policy();
  static_assert(sizeof(t6) == 2 * sizeof(int), "unexpected EBO");

  test<float> t7 {0.5f};
  t7.show_policy();
  static_assert(sizeof(t7) == sizeof(float_and_int), "strange");

  test<std::string> t8 {"be nice"};
  t8.show_policy();
  static_assert(sizeof(t8) == sizeof(string_and_int), "strange");

  test<std::string> t9 {};
  t9.show_policy();
}

I am looking for code reviews that address the following questions.

  • Is the approach general and safe? (Are there Ts for which it breaks?)
  • Does it cause any overhead that could be avoided?
  • Am I using noexcept correctly?
  • Should I use uniform initialization syntax instead? (I've chosen not to because that's what the constructors of the standard library's smart pointers do as well.)

I believe that such utility could be of general use and – depending on the kind of feedback I'll get from this thread – am thinking about proposing it as an addition to the standard library.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ May be the CRTP is a better pattern to go for implementing policies and mixin interfaces. Though that's too narrow for an answer here, thus I'm giving that hint as a comment. \$\endgroup\$ Dec 15 '15 at 19:37
5
\$\begingroup\$

Code looks fine, I have one important issue and some minor comments:

Typedefs

One of the common uses of policy classes are their typedefs (cf. std::iterator<>). Using policy_base_t<P> hides all of those, since in the delegating case, you hid P, and in the deriving case, you inherited privately. So I would suggest at the very least deriving publically from PolicyT and exposing it as a typedef in both cases:

using Policy = PolicyT;

That at least would handle most of the cases. Presumably nobody is adding typedefs to their final policy right?

Inherit constructors too

Rather than explicitly forwarding args to the constructor in deriving_policy, you could just using PolicyT::PolicyT; Does the same thing and saves a bunch of typing.

Unecessary this->

In delegating_policy, you don't need this-> where you use it, you can just write policy_. It's a member, so it's not dependent.

Excessive vertical spacing

You don't need to double space around accessor labels or namespaces. It just makes it harder to fit everything on one window.

Use the aliases

Prefer the C++14 std::conditional_t to typename std::conditional<blah, blah, blah>::type. If you don't have a C++14 compiler, write the alias somewhere and still use it.

\$\endgroup\$
2
  • \$\begingroup\$ I like the suggested typedef. While the user of the policy always knows its type (the T used to instantiate policy_base_t), it's easy to do and might be useful. I went for private inheritance to give either wrapper type the same interface, preventing people to rely on something that is only available if T is not final. There is no way to make the typedefs available in the delegating_policy or am I missing something? \$\endgroup\$
    – 5gon12eder
    Dec 15 '15 at 21:40
  • \$\begingroup\$ @5gon12eder Nope, definitely no way. Unless you know exactly what they are up front and just manually write them. \$\endgroup\$
    – Barry
    Dec 15 '15 at 21:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.