Circular dependency with using directive where forward declarations don't work

Viewed 81

I'm working with a codebase where I can't solve this circular dependency:

foo.h

class Foo
{
public:
  using Ptr = std::shared_ptr<Foo>;
  using ConstPtr = std::shared_ptr<const Foo>;

  void setter(const Bar::Ptr& bar_ptr) {};

private:
  Bar::WeakPtr bar_ptr_;
};

and

bar.h

class Bar
{
public:
  using Ptr = std::shared_ptr<Bar>;
  using ConstPtr = std::shared_ptr<const Bar>;
  using WeakPtr = std::weak_ptr<Bar>;

  Bar(Foo::ConstPtr foo_ptr) : foo_ptr_(std::move(foo_ptr)) {};

private:
  Foo::ConstPtr foo_ptr_;
};

It was previously compiling because there was an external header where:

using FooPtr = std::shared_ptr<Foo>;
using FooConstPtr = std::shared_ptr<const Foo>;
using BarPtr = std::shared_ptr<Bar>;

But since consistency is sought after, I'd like to have Foo::Ptr, Foo::ConstPtr, Bar::Ptr. Any chance I can get it?

Coliru

EDIT: added the Bar::WeakPtr which was previously missing since I thought I was already in trouble.

2 Answers

This is arguably a deficiency of C++. You cannot declare just some public names of a class without including the whole class definition. In this setting, I am afraid your circular dependency cannot be resolved - at least not in a way that won't bite you in some way in the future.

However, as an alternative, consider using a template definitions outside of the subject class. It changes the order of terms, but not the overall meaning:

template<class T>
struct PtrStruct {
    using type = std::shared_ptr<T>;
};

template<class T>
using Ptr = typename PtrStruct<T>::type;

template<class T>
struct ConstPtrStruct {
    using type = std::shared_ptr<const T>;
};

template<class T>
using ConstPtr = typename PtrStruct<const T>::type;

In this setting your Foo::Ptr becomes Ptr<Foo> and Foo::ConstPtr becomes ConstPtr<Foo>. You still can:

  • at a later stage replace std::shared_ptr with something different at a single place where PtrStruct is defined. Rest of the code will compile without a change, as long as the used interface didn't change.
  • specialize PtrStruct for specific T if it should be something different than the rest.

If consistency is sought after, do not create ill-formed structures. You can try something like:


// Foo.h

class Bar;

class Foo
{
public:
  using BarPtr = std::shared_ptr<Bar>;

  void setter( BarPtr bar_ptr)
  {}
};


// Bar.h

class Foo;

class Bar
{
public:
  using ConstFooPtr=std::shared_ptr<const Foo>;

  Bar( ConstFooPtr foo_ptr)
      : foo_ptr_( std::move( foo_ptr))
  {}

private:
  ConstFooPtr foo_ptr_;
};

Related